在JavaScript中,全部代碼都是單線程。因爲該「缺陷」,JavaScript在處理網絡操做、事件操做時都是須要進行異步執行的。AJAX就是一個典型的異步操做javascript
對於異步操做,有傳統的利用回調函數和使用 Promise,兩者的對好比下:java
//以往回調方式 函數1(function(){ //代碼執行...(ajax1) 函數2(function(){ //代碼執行...(ajax2) 函數3(function(data3){ //代碼執行...(ajax3) }); ... }); }); //Promise回調方式:鏈式調用,可構建多個回調函數。 //例如請求一個ajax以後,須要這個拿到這個ajax的數據去請求下一個ajax promise().then().then()...catch()
對比可知,使用傳統回調函數方式處理異步操做很複雜。爲了解決這樣的問題,commonJS引入了Promise概念,很好的解決了JavaScript的異步操做。ajax
概念編程
Promise是異步編程的一種解決方案,比傳統的解決方案(回調函數和事件)更簡單易理解且實用,因此Promise簡單來講就是一個容器,裏面保存着某個將來纔會執行事件(一般爲一個異步操做)的結果。數組
特色promise
語法服務器
//建立Promise實例 let promise = new Promise( (resolve,reject) =>{ // 執行相應代碼並根據狀況調用resolve或者reject ... } ) // 在promise的then方法中執行回調 Promise.then( function(){ // 第一個參數是返回resolve狀態時執行的回調函數 },function(){ // 第二個參數是返回reject狀態時執行的回調函數 } )
狀態網絡
Promise對象表明一個異步操做,有三種狀態異步
pending(等待)、resolved(成功狀態)、rejected(失敗狀態)async
兩種狀態改變方式:pending => resolved,pending => rejected
注:只有異步操做的結果才能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態
缺點
用法 先看下面這個例子:
1 setTimeout( ()=>{ 2 console.log('123'); 3 },0 ) 4 console.log('456');
執行結果相信你們都知道,上面console處於異步代碼中(即便延遲爲0),下面console處於同步代碼中,若是想要 ‘456’ 在 ‘123’ 執行結束後再輸出呢?
傳統回調函數方式:
1 setTimeout( ()=>{ 2 console.log('123'); 3 fn(); 4 },0 ) 5 function fn(){ 6 console.log('456') 7 }
8 // 123
9 // 456
使用 Promise 方式:
1 let promise = new Promise( (resolve,reject) =>{ 2 setTimeout( ()=>{ 3 console.log('123'); 4 resolve('456'); 5 } ,0) 6 } ) 7 promise.then(function(data){ 8 // resolve狀態 9 console.log(data); 10 },function(error){ 11 // reject狀態 12 })
13 // 123
14 // 456
Promise 實例生成之後,能夠用 then 方法分別指定 resolved 狀態和 rejected 狀態的回調函數。也就是說,狀態由實例化時的參數(兩個不一樣狀態的回調函數)執行來決定的,根據不一樣的
狀態來執行具體哪一個函數。
注:resolve() 和 reject() 的闡述會傳遞到對應的回調函數的 data 或者 error,且 then 返回的是一個新的 Promise 實例,也就是或還能夠繼續 then
鏈式操做用法 從 表面行看,咱們或許會以爲 Promise 只是可以簡化傳統的層層回調寫法。然而,Promise的精髓是 ‘狀態’,用維護狀態,傳遞狀態的方式來使得回調函數可以及時調用,它比傳遞 callback 函數要靈活、簡單的多。下面爲一個 Promise 的通常使用場景:
1 async1() 2 .then(function(data){ 3 console.log(data); 4 return async2(); 5 }) 6 .then(function(data){ 7 console.log(data); 8 return async3(); 9 }) 10 .then(function(data){ 11 console.log(data); 12 }); 13 14 function async1(){ 15 let p = new Promise(function(resolve,reject){ 16 // 異步操做 17 setTimeout(()=>{ 18 console.log('異步任務1執行完成!'); 19 resolve('數據1'); 20 },1000); 21 }); 22 return p; 23 }; 24 function async2(){ 25 let p = new Promise((resolve,reject)=>{ 26 // 異步操做 27 setTimeout(()=>{ 28 console.log('異步任務2執行完成!'); 29 resolve('數據2'); 30 },2000); 31 }); 32 return p; 33 }; 34 function async3(){ 35 let p = new Promise((resolve,reject)=>{ 36 // 異步操做 37 setTimeout(()=>{ 38 console.log('異步任務3執行完成!'); 39 resolve('數據3'); 40 },3000); 41 }); 42 return p; 43 } 44 // 1秒後... 45 // 異步任務1執行完成! 46 // 數據1 47 // 2秒後... 48 // 異步任務2執行完成! 49 // 數據2 50 // 3秒後... 51 // 異步任務3執行完成! 52 // 數據3
在 then 方法中,能夠不用 return Promise實例對象,直接返回數據在後面的 then 中也可以接收到數據
reject用法 在前面的例子中只有 resolve(成功狀態)的回調,實際應用中還會有失敗狀態,reject 就是把 Promise 的狀態設置爲 rejected ,這樣咱們就可以在 then 中捕捉到,而後執行相應的回調
1 let num = 10; 2 let p1 = function(){ 3 return new Promise((resolve,reject)=>{ 4 if(num <= 5){ 5 resolve("<=5,走resolve"); 6 console.log("resolve不能結束Promise"); 7 } 8 else{ 9 reject(">5,走reject"); 10 console.log("reject不能結束Promise") 11 } 12 }) 13 } 14 15 p1() 16 .then(function(data){ 17 console.log(data) 18 },function(error){ 19 console.log(error) 20 }) 21 // reject不能結束Promise 22 // >5,走reject
resolve 和 reject 永遠在當前環境的最後面執行,因此後面的同步代碼會先執行
若是 resolve 和 reject 以後還有代碼須要執行,最好放在 then 裏,而後在 resolve 和 reject 前面寫上 return
Promise.prototype.catch() Promise.prototype.catch() 方法是 .then( null, rejection ) 的別名,用於指定發生錯誤的回調函數
1 p1() 2 .then(function(data){ 3 console.log(data) 4 }) 5 .catch(function(err){ 6 console.log(err) 7 }) 8 //reject不能結束Promise 9 //>5,走reject
Promise.all()
Promise.all() 方法用於將多個 Promise 實例包裝成一個新的 Promise 實例
1 const p = Promise.all( [p1,p2,p3] );
p 的狀態由 p一、p二、p3 決定,分爲兩種狀況:
因爲p 是包含3個 Promise 實例的數組,只有這三個實例狀態都爲 resolved,或者其中有一個及以上的實例狀態爲 rejected 時,纔會調用 Promise.all 方法後面的回調函數
若是做爲參數的 Promise 實例本身定義了 catch 方法,那麼它一旦被 rejected,並不會觸發 Promise.all 的 catch 方法,若是沒有實例參數定義本身的 catch,就會調用 Promise.all 的 catch 方法
Promise.race() Promise.race() 方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例
1 const p = Promise.race( [p1,p2,p3] )
使用該方法時,只要 p一、p二、p3 之中有一個實例率先改變狀態,p 的狀態就會跟着該實例變化,該實例的返回值傳遞給 p 的回調函數
Promise.resolve() 在實際應用中有時須要將一個對象轉爲 Promise 對象,Promise.resolve() 方法就可以實現,該實例的狀態爲 resolved
const promise = Promise.resolve( '123' ); // 等價於 Promise.resolve( '123' ); new Promise( resolve => resolve( '123' ) )
Promise.resolve 方法的參數類型有四種狀況:
let thenable = { then: function( resolve,reject ){ resolve( 'aaa' ); } }
let thenable = { then: function(resolve, reject) { resolve( 'aaa' ); } }; let p1 = Promise.resolve(thenable); p1.then(function(value) { console.log(value); // aaa })
上面代碼中,thenable 對象的 then 方法執行後,對象 p1 的狀態就變爲 resolved,從而當即執行後面那個 then 方法指定的回調函數,輸出 aaa
1 const p = Promise.resolve('Hello'); 2 3 p.then(function (s){ 4 console.log(s) 5 }); 6 // Hello
上面代碼生成一個新的 Promise 對象的實例 p,因爲字符串 Hello 不屬於異步操做( String 對象不具備 then 方法),返回 Promise 實例的狀態生成就爲 resolved,因此回調函數會當即執行,且 Promise.resolve 方法的參數會同時傳給回調函數
1 const p = Promise.resolve(); 2 3 p.then(function () { 4 // ... 5 });
上面變量 p 就是一個 Promise 對象,注:當即 resolve 的 Promise 對象,是在本輪「事件循環」(event loop)的結束時,而不是在下一輪「事件循環」的開始時
1 setTimeout(function () { 2 console.log('three'); 3 }, 0); 4 5 Promise.resolve().then(function () { 6 console.log('two'); 7 }); 8 9 console.log('one'); 10 11 // one 12 // two 13 // three
上面代碼中,setTimeout(fn,0)在下一輪「事件循環」開始時執行,Promise。resolve() 在本輪執行,console.log('one')則是當即執行,所以最早輸出
Promise.reject()
Promise.reject( reason ) 方法也會返回一個新的 Promise 實例,該實例的狀態爲 rejected
1 const p = Promise.reject('出錯了'); 2 // 等同於 3 const p = new Promise((resolve, reject) => reject('出錯了')) 4 5 p.then(null, function (s) { 6 console.log(s) 7 }); 8 // 出錯了
上面代碼生成一個 Promise 對象的實例 p,狀態爲rejected,回調函數會當即執行
注:Promise.reject() 方法的參數,會原封不動的做爲 reject 的理由,變成後續方法的參數。這一點與 Promise.resolve 方法不一致
1 const thenable = { 2 then(resolve, reject) { 3 reject('出錯了'); 4 } 5 }; 6 7 Promise.reject(thenable) 8 .catch(e => { 9 console.log(e === thenable) 10 }) 11 // true
上面代碼中, Promise.reject 方法的參數爲一個 thenable 對象,執行之後,後面 catch 方法的參數不是 reject 拋出的 ‘出錯了’ 這個字符串,而是 thenable 對象
兩個附加的方法
ES6 的 Promise 提供的 API 不是不少,有些有用的方法能夠本身部署,下面介紹兩個不在 ES6 中,但頗有用的方法
done()
Promise 對象的回調鏈,無論以 then 方法或者 catch 方法結尾,要是最後一個方法拋出錯誤,都有可能沒法捕捉到(Promise內部的錯誤不會冒泡到全局)。所以,能夠提供一個 done 方法,老是處於回調鏈的末端,保證拋出任何可能出現的錯誤被捕捉
1 asyncFunc() 2 .then(f1) 3 .catch(r1) 4 .then(f2) 5 .done();
實例:
1 Promise.prototype.done = function (onFulfilled, onRejected) { 2 this.then(onFulfilled, onRejected) 3 .catch(function (reason) { 4 // 拋出一個全局錯誤 5 setTimeout(() => { throw reason }, 0); 6 }); 7 };
上面代碼可見,done 方法的使用,能夠像 then 方法那樣用,提供 resolved 和 rejected 狀態的回調函數,也能夠不提供任何參數。總之,done 都可以捕捉到任何可能出現的錯誤,並向全局拋出
finally()
finally 方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做。它與 done 方法最大的區別是 finally 方法可以接收一個普通的回調函數做爲參數,該函數無論怎樣都必須執行
實例:服務器使用 Promise 處理請求,而後使用 finally 方法關掉服務器
1 server.listen(0) 2 .then(function () { 3 // run test 4 }) 5 .finally(server.stop);
1 Promise.prototype.finally = function (callback) { 2 let P = this.constructor; 3 return this.then( 4 value => P.resolve(callback()).then(() => value), 5 reason => P.resolve(callback()).then(() => { throw reason }) 6 ); 7 };
上面代碼中,無論前面的 Promise 是resolved 狀態仍是 rejected 狀態,都會執行回調函數callback