Promise詳解

Promise
介紹:node

  1. 用於異步計算
  2. 將異步操做隊列化,按照指望的順序執行,返回符合預期的結果
  3. 能夠在對象之間傳遞和操做promise,幫助咱們處理隊列

因爲promise是控制異步操做的,因此先來介紹一下在promise以前異步操做的常見語法。git

  1. 事件偵聽與響應
  2. 回調函數(例如ajax請求回調)

異步回調的問題:ajax

  1. 回調地獄問題(一個回調嵌入一個回調,特別是一些數據庫操做和文件操做 , 難以維護)
  2. 沒法正常使用 return 和 throw
  3. 異步回調的回調函數都是在一個新棧中,因此在如今的棧沒法獲取到先前棧的信息。以前的棧也捕獲不到當前棧拋出的錯誤,因此在異步回調中沒法正常使用try catch正常處理錯誤。
  4. 在異步回調中常常須要在外層回調中去定義一些變量給內層回調使用。

talk is cheap , show me the code數據庫

const path = require('path');
const fs = require('fs');
//尋找最大文件的函數
function findLargest(dir,callback){
    fs.readdir(dir,function(err,files){
        if(err) return callback(err); //[錯誤使用回調來處理]
            let count = files.length; //獲取文件長度
            let errored = false; //是否錯誤
            let stats = [];
            //遍歷文件夾下的全部文件
            files.forEach(file => {
                fs.stat(path.join(dir,file),(err,stat) =>{
                    if(errored) return;
                    if(err){
                        errored = true;
                        return callback(err);
                    }
                    stats.push(stat);
                    if(--count === 0){
                        let largest = stats
                        .filter(function(stat){
                            console.log('-----');
                            console.log(stat.isFile());
                            return stat.isFile();
                        }) //先判斷是不是文件
                        .reduce(function(prev,next){
                            //判斷大小
                            if(prev.size > next.size) { return prev; }
                            return next;
                        });
                    callback(null,files[stats.indexOf(largest)])
                }
            })
        })
    })
}

findLargest('../blog/blogDemo/移動端滾動詳解demo',function(err,filename){
    if(err) return console.error(err);
    console.log('largest file was:',filename);
})

上面就是一個查找最大文件的例子,其中有許多回調帶來的問題。接下來咱們先回歸主題,學習一些promise的使用,而後使用promise來改寫這個例子。數組

promise詳解promise

new Promise(
    /*實例化Promise時傳入一個執行器,也就是一個函數*/
    function(resolve,reject){
        //異步操做放在這裏

        resolve(); //處理成功,修改實例化的promise對象的狀態爲fulfilled
        reject(); //處理失敗,修改實例化的promise對象的狀態爲rejected
    }
)
.then(function A(){
    //成功以後的處理,即調用resolve()就執行A中的內容
},function B(){
    //失敗以後的處理,即調用reject()或者拋出了錯誤,就執行B中的內容
})

promise有三個狀態:異步

pending 【待定】初始狀態
fulfilled 【實現】操做成功
rejected 【否決】操做失敗函數

promise的狀態一發生改變,立馬調用.then()中的響應函數處理後續步驟,若是then()中返回了一個新的promise實例,則繼續循環下去。學習

promise經常使用的場景:ui

console.log('start');
new Promise(function(resolve,reject){
    setTimeout(function(){    //定時器模擬異步
        resolve('hello');    //修改promise狀態調用then中的第一個函數
    },2000);
}).then((value)=>{
    console.log(value);    //接收resolve傳來的值
    return new Promise(function(resolve){   //該then()返回一個新的promise實例,後面能夠繼續接then
        setTimeout(function(){
            resolve('world');       //修改新promise的狀態,去調用then
        },3000)
    })  
}).then((value)=>{
   console.log(value);
})

//輸出結果:
/*
    當即輸出   start
    兩秒輸出   hello
    再三秒     world
*/

上面咱們在 then() 函數中返回的是一個新的promise,若是返回的不是一個新的promise會怎樣呢?依然是上面的代碼,稍做修改。

console.log('start');
new Promise(function(resolve,reject){
    setTimeout(function(){  
        resolve('hello');    
    },2000);
}).then((value)=>{
    console.log(value);  
    (function(){
        return new Promise(function(resolve){   
            setTimeout(function(){
                resolve('world');       
            },3000)
        })  
    })();  
    return false; 
}).then((value)=>{
   console.log(value);
})
/*
    結果:
       當即輸出   start
       兩秒輸出   hello
       三秒輸出   flase
*/

根據上面的運行結果來看,若是在一個then()中沒有返回一個新的promise,則return 什麼下一個then就接受什麼,在上面的實例代碼中return的是false,下一個then中接受到的value就是false,若是then中沒有return,則默認return的是undefined.

注意:then中return Promise必須是在then函數的做用域中return,不能在其餘函數做用域中return,無效。上面的例子中return Promise就是在一個當即執行函數中返回的,因此無效。

.then()中包含.then()的嵌套狀況
then()的嵌套會先將內部的then()執行完畢再繼續執行外部的then();在多個then嵌套時建議將其展開,將then()放在同一級,這樣代碼更清晰。

console.log('start');
new Promise((resolve,reject)=>{
    setTimeout(function(){
        console.log('step');
        resolve(110);
    },1000)
})
.then((value)=>{
    return new Promise((resolve,reject)=>{
        setTimeout(function(){
            console.log('step1');
            resolve(value);
        },1000)
    })
    .then((value)=>{
        console.log('step 1-1');
        return value;
    })
    .then((value)=>{
        console.log('step 1-2');
        return value;
    })
})
.then((value)=>{
    console.log(value);
    console.log('step 2');
})
/*
 start
 step
 step1
 step 1-1
 step 1-2
 110
 step 2
*/

//展開以後的代碼
console.log('start');
new Promise((resolve,reject)=>{
    setTimeout(function(){
        console.log('step');
        resolve(110);
    },1000)
})
.then((value)=>{
    return new Promise((resolve,reject)=>{
        setTimeout(function(){
            console.log('step1');
            resolve(value);
        },1000)
    })
})
.then((value)=>{
        console.log('step 1-1');
        return value;
    })
.then((value)=>{
    console.log('step 1-2');
    return value;
})
.then((value)=>{
    console.log(value);
    console.log('step 2');
})

錯誤處理
promise處理錯誤有兩種方式,一種是發現錯誤執行then中的第二個回調函數來處理錯誤,一種是.catch()來處理錯誤

注意:拋出ERROR時,只有在執行函數的頂層拋出後面的catch纔會接受到。這裏若是在setTimeout中拋出錯誤,catch和then中的錯誤處理函數是接受不到的

//1.根據錯誤執行then中第二個回調來處理錯誤
new Promise((resolve,reject)=>{
    setTimeout(function(){
        //只要出現了錯誤或者調用了reject,就能夠在then的第二個函數中獲取到
        reject('err');
    },1000)
}).then((value)=>{
    console.log(value);
},
//出錯以後的執行函數
(err)=>{
    console.log('出錯了');
    console.log(err);
})
/*
    出錯了
    err
*/

//2.根據catch來獲取錯誤,拋出err或者執行reject()都會在catch中獲取到錯誤信息
new Promise((resolve,reject)=>{
    //只要出現了錯誤,就能夠在then的第二個函數中獲取到
    setTimeout(function(){
        reject('一個錯誤');
    },1000)
}).then((value)=>{
    console.log(value);
}).catch(err=>{
    console.log('錯誤信息:'+err);
})
/*
    錯誤信息:一個錯誤
*/

更推薦使用catch的方式進行處理錯誤,由於catch能獲取到以前全部then中出現的錯誤

catch和then的連用
若是每一步都有可能出現錯誤,那麼就可能出現catch後面接上then的狀況。上代碼

new Promise((resolve,reject)=>{
    resolve();
})
.then(value=>{
    console.log('done 1');
    throw new Error('done 1 error');
})
.catch(err=>{
    console.log('錯誤信息1:'+err);
})
.then(value=>{
    console.log('done 2');
})
.catch(err=>{
    console.log('錯誤信息2:'+err);
})
/*
 done 1
 錯誤信息1:Error: done 1 error
 done 2

 說明catch後面會繼續執行then,catch返回的也是一個promise實例
*/
new Promise((resolve,reject)=>{
    resolve();
})
.then(value=>{
    console.log('done 1');
    throw new Error('done 1 error');
})
.catch(err=>{
    console.log('錯誤信息1:'+err);
    throw new Error('catch error');
})
.then(value=>{
    console.log('done 2');
})
.catch(err=>{
    console.log('錯誤信息2:'+err);
})
/*
 done 1
 錯誤信息1:Error: done 1 error
 錯誤信息2:Error: catch error

 若是在catch中也拋出了錯誤,則後面的then的第一個函數不會執行,由於返回的promise狀態已經爲rejected了
*/

總的來講,catch以後能夠接then,catch也是返回的一個promise對象。若是catch中出現錯誤,則promise狀態修改爲reject,不然爲fullfilled狀態

Promise.all()
將多個Promise批量執行,全部的Promise都完畢以後返回一個新的Promise。

  1. 接收一個數組做爲參數
  2. 數組中能夠是Promise實例,也能夠是別的值,只有Promise會等待狀態的改變
  3. 全部子Promise完成,則該Promise完成,而且返回值是參數數組中全部Promise實例的結果組成的數組
  4. 有任何一個Promise失敗,則該Promise失敗,返回值是第一個失敗的Promise的結果
console.log('here we go');
Promise.all([1,2,3])
    .then(all=>{
        console.log('1: ' + all); 
        return Promise.all([function(){
            console.log('ooxx');
        },'xxoo',false])
    })
    .then(all=>{
        console.log('2: ' + all);
        let p1 = new Promise(resolve=>{
            setTimeout(function(){
                resolve('I\'m p1');
            },1500)
        });
        let p2 = new Promise(resolve=>{
            setTimeout(function(){
                resolve('I\'m p2');
            },2000)
        });
        return Promise.all([p1,p2]);
    })
    .then(all=>{
        console.log('3: '+all);
        let p1 = new Promise((resolve,reject)=>{
            setTimeout(function(){
                resolve('P1');
            },1000)
        })
        let p2 = new Promise((resolve,reject)=>{
            setTimeout(function(){
                reject('P2');
            },3000)
        })
        let p3 = new Promise((resolve,reject)=>{
            setTimeout(function(){
                reject('P3');
            },2000)
        })
        return Promise.all([p1,p2,p3]);
    })
    .then(all=>{
        console.log('all: ' + all);
    })
    .catch(err=>{
        console.log('Catch:' + err);
    })
    /*
         here we go
         1: 1,2,3
         2: function(){
            console.log('ooxx');
            },xxoo,false
         3: I'm p1,I'm p2    
         Catch:P3      

         證實了上面的四點。
    */

Promise.race()
和Promise.all()差很少,區別就是傳入的數組中有一個Promise完成了則整個Promise完成了。

let p1 = new Promise(resolve=>{
    setTimeout(function(){
        resolve('p1');
    },10000);
})
let p2 = new Promise(resolve=>{
    setTimeout(function(){
        resolve('p2');
    },1000);
})
Promise.race([p1,p2])
.then((value)=>{
    console.log(value);
})
/*
    p1     1s以後輸出
    。。    等待十秒後代碼纔算執行完畢
*/

常見用法:

將定時器和異步操做放在一塊兒,若是定時器先觸發,則認爲超時,告知用戶。

let p1 = new Promise(resolve=>{
    $.ajax({
        success:function(result){
            resolve(result);
        }
    }); //異步操做
})
let p2 = new Promise(resolve=>{
    setTimeout(function(){
        resolve('timeout');
    },10000);
})
Promise.race([p1,p2])
.then(value=>{
    if(value === 'timeout'){
        alert('請求超時');
    }
})

將回調包裝爲Promise
好處:1.可讀性好 2. 返回的結果能夠放在任意Promise隊列

// 將nodejs中fs模塊的readDir和readFile方法包裝爲Promise
//FileSystem.js
const fs = require('fs');
module.exports = {
    readDir: function(path,options){
        return new Promise(resolve=>{
            fs.readdir(path,options,(err,files)=>{
               if(err){
                   throw err;
               }
               resolve(files);
            })
        })
    },
    readFile: function(path,options){
        return new Promise(resolve=>{
            fs.readFile(path,options,(err,content)=>{
                if(err) throw err;
                resolve(content);
            })
        })
    }
}

//test.js
const fs = require('./FileSystem');
fs.readFile('./test.txt','utf-8')
    .then(content=>{
       console.log(content);
    })

尋找最大文件Promise改版

const fs = require('fs');
const path = require('path');
const FileSystem = require('./FileSystem'); //用上面封裝的FileSystem

function findLargest(dir) {

    return FileSystem
        .readDir(dir, 'utf-8')
        .then(files => {
            return Promise.all(files.map(file=>{
                return new Promise(resolve =>{
                    fs.stat(path.join(dir,file),(err,stat)=>{
                        if err throw err;
                        if(stat.isDirectory()){
                            return resolve({
                                size: 0
                            });
                        }
                        stat.file = file;
                        resolve(stat);
                    });
                });
            }));
        })
        .then( stats =>{
            let biggest = stats.reduce((memo,stat)=>{
                if(memo.size < stat.size){
                    return stat;
                }
                return memo;
            });
            return biggest.file;
        })
}

個人文章均可以在個人gitbook上找到。歡迎star哈哈!gitbook地址

相關文章
相關標籤/搜索