Promise
介紹:node
因爲promise是控制異步操做的,因此先來介紹一下在promise以前異步操做的常見語法。git
異步回調的問題:ajax
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。
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地址