自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

JavaScript異步與Promise實(shí)現(xiàn)

開發(fā) 前端
在閱讀本文之前,你應(yīng)該已經(jīng)了解JavaScript異步實(shí)現(xiàn)的幾種方式:回調(diào)函數(shù),發(fā)布訂閱模式,Promise,生成器(Generator),其實(shí)還有async/await方式,這個(gè)后續(xù)有機(jī)會會介紹。本篇將介紹Promise,讀完你應(yīng)該了解什么是Promise,為什么使用Promise,而不是回調(diào)函數(shù),Promise怎么使用,使用Promise需要注意什么,以及Promise的簡單實(shí)現(xiàn)。

【引自熊建剛的博客】前言

如果你已經(jīng)對JavaScript異步有一定了解,或者已經(jīng)閱讀過本系列的其他兩篇文章,那請繼續(xù)閱讀下一小節(jié),若你還有疑惑或者想了解JavaScript異步機(jī)制與編程,可以閱讀一遍這兩篇文章:

回調(diào)函數(shù)

回調(diào)函數(shù),作為JavaScript異步編程的基本單元,非常常見,你肯定對下面這類代碼一點(diǎn)都不陌生:

  1. component.do('purchase', funcA); 
  2.     function funcA(args, callback) { 
  3.         //... 
  4.         setTimeout(function() { 
  5.             $.ajax(url, function(res) { 
  6.                 if (res) { 
  7.                     callback(res) 
  8.                 } else {//...} 
  9.             }); 
  10.         }, 300); 
  11.         funcB(); 
  12.         setTimeout(function() { 
  13.             $.ajax(arg, function(res) { 
  14.                 if (res) { 
  15.                     callback(res); 
  16.                 } 
  17.             }); 
  18.         }, 400); 
  19.     }  

上面這些代碼,一層一層,嵌套在一起,這種代碼通常稱為回調(diào)地獄,無論是可讀性,還是代碼順序,或者回調(diào)是否可信任,亦或是異常處理角度看,都是不盡人意的,下面做簡單闡述。

順序性

上文例子中代碼funcB函數(shù),還有兩個(gè)定時(shí)器回調(diào)函數(shù),回調(diào)內(nèi)各自又有一個(gè)ajax異步請求然后在請求回調(diào)里面執(zhí)行最外層傳入的回調(diào)函數(shù),對于這類代碼,你是否能明確指出個(gè)回調(diào)的執(zhí)行順序呢?如果funcB函數(shù)內(nèi)還有異步任務(wù)呢?,情況又如何?

假如某一天,比如幾個(gè)月后,線上出了問題,我們需要跟蹤異步流,找出問題所在,而跟蹤這類異步流,不僅需要理清個(gè)異步任務(wù)執(zhí)行順序,還需要在眾多回調(diào)函數(shù)中不斷地跳躍,調(diào)試(或許你還能記得諸如funcB這些函數(shù)的作用和實(shí)現(xiàn)),無論是出于效率,可讀性,還是出于人性化,都不希望開開發(fā)者們再經(jīng)歷這種痛苦。

信任問題

如上,我們調(diào)用了一個(gè)第三方支付組件的支付API,進(jìn)行購買支付,正常情況發(fā)現(xiàn)一切運(yùn)行良好,但是假如某一天,第三方組件出問題了,可能多次調(diào)用傳入的回調(diào),也可能傳回錯(cuò)誤的數(shù)據(jù)。說到底,這樣的回調(diào)嵌套,控制權(quán)在第三方,對于回調(diào)函數(shù)的調(diào)用方式、時(shí)間、次數(shù)、順序,回調(diào)函數(shù)參數(shù),還有下一節(jié)將要介紹的異常和錯(cuò)誤都是不可控的,因?yàn)闊o論如何,并不總能保證第三方是可信任的。

錯(cuò)誤處理

關(guān)于JavaScript錯(cuò)誤異常,初中級開發(fā)接觸的可能并不多,但是其實(shí)還是有很多可以學(xué)習(xí)實(shí)踐的地方,如前端異常監(jiān)控系統(tǒng)的設(shè)計(jì),開發(fā)和部署,并不是三言兩語能闡述的,之后會繼續(xù)推出相關(guān)文章。

錯(cuò)誤堆棧

我們知道當(dāng)JavaScript拋出錯(cuò)誤或異常時(shí),對于未捕獲異常,瀏覽器會默認(rèn)在控制臺輸出錯(cuò)誤堆棧信息,如下,當(dāng)test未定義時(shí):

  1. function init(name) { 
  2.        test(name
  3.    } 
  4.  
  5.    init('jh');  

輸出如圖: 

 

 

 

如圖中自頂向下輸出紅色異常堆棧信息,Uncaught表示該異常未捕獲,ReferenceError表明該異常類型為引用異常,冒號后是異常的詳細(xì)信息:test is not defined,test未定義;后面以at起始的行就是該異常發(fā)生處的調(diào)用堆棧。第一行說明異常發(fā)生在init函數(shù),第二行說明init函數(shù)的調(diào)用環(huán)境,此處在控制臺直接調(diào)用,即相當(dāng)于在匿名函數(shù)環(huán)境內(nèi)調(diào)用。

異步錯(cuò)誤堆棧

上面例子是同步代碼執(zhí)行的異常,當(dāng)異常發(fā)生在異步任務(wù)內(nèi)時(shí),又會如何呢?,假如把上例中代碼放在一個(gè)setTimeout定時(shí)器內(nèi)執(zhí)行:

  1. function init(name) { 
  2.     test(name
  3. setTimeout(function A() { 
  4.     setTimeout(function() { 
  5.         init(); 
  6.     }, 0); 
  7. }, 0);  

如圖: 

 

 

 

可以看到,異步任務(wù)中的未捕獲異常,也會在控制臺輸出,但是setTimeout異步任務(wù)回調(diào)函數(shù)沒有出現(xiàn)在異常堆棧,為什么呢?這是因?yàn)楫?dāng)init函數(shù)執(zhí)行時(shí),setTimeout的異步回調(diào)函數(shù)不在執(zhí)行棧內(nèi),而是通過事件隊(duì)列調(diào)用。

JavaScript錯(cuò)誤處理

JavaScript的異常捕獲,主要有兩種方式:

  • try{}catch(e){}主動(dòng)捕獲異常; 

 

 

 

如上,對于同步執(zhí)行大代碼出現(xiàn)異常,try{}catch(e){}是可以捕獲的,那么異步錯(cuò)誤呢? 

 

 

 

如上圖,我們發(fā)現(xiàn),異步回調(diào)中的異常無法被主動(dòng)捕獲,由瀏覽器默認(rèn)處理,輸出錯(cuò)誤信息。

window.onerror事件處理器,所有未捕獲異常都會自動(dòng)進(jìn)入此事件回調(diào) 

 

 

 

如上圖,輸出了script error錯(cuò)誤信息,同時(shí),你也許注意到了,控制臺依然打印出了錯(cuò)誤堆棧信 息,或許你不希望用戶看到這么醒目的錯(cuò)誤提醒,那么可以使window.onerror的回調(diào)返回true即可阻止瀏覽器的默認(rèn)錯(cuò)誤處理行為: 

 

 

 

當(dāng)然,一般不隨意設(shè)置window.onerror回調(diào),因?yàn)槌绦蛲ǔ?赡苄枰渴鹎岸水惓1O(jiān)控系統(tǒng),而通常就是使用window.onerror處理器實(shí)現(xiàn)全局異常監(jiān)控,而該事件處理器只能注冊一個(gè)回調(diào)。

回調(diào)與Promise

以上我們談到的諸多關(guān)于回調(diào)的不足,都很常見,所以必須是需要解決的,而Promise正是一種很好的解決這些問題的方式,當(dāng)然,現(xiàn)在已經(jīng)提出了比Promise更先進(jìn)的異步任務(wù)處理方式,但是目前更大范圍使用,兼容性更好的方式還是Promise,也是本篇要介紹的,之后會繼續(xù)介紹其他處理方式。

Promises/A+

分析了一大波問題后,我們知道Promise的目標(biāo)是異步管理,那么Promise到底是什么呢?

  • 異步,表示在將來某一時(shí)刻執(zhí)行,那么Promise也必須可以表示一個(gè)將來值;
  • 異步任務(wù),可能成功也可能失敗,則Promise需要能完成事件,標(biāo)記其狀態(tài)值(這個(gè)過程即決議-resolve,下文將詳細(xì)介紹);
  • 可能存在多重異步任務(wù),即異步任務(wù)回調(diào)中有異步任務(wù),所以Promise還需要支持可重復(fù)使用,添加異步任務(wù)(表現(xiàn)為順序鏈?zhǔn)秸{(diào)用,注冊異步任務(wù),這些異步任務(wù)將按注冊的順序執(zhí)行)。

所以,Promise是一種封裝未來值的易于復(fù)用的異步任務(wù)管理機(jī)制。

為了更好的理解Promise,我們介紹一下Promises/A+,一個(gè)公開的可操作的Promises實(shí)現(xiàn)標(biāo)準(zhǔn)。先介紹標(biāo)準(zhǔn)規(guī)范,再去分析具體實(shí)現(xiàn),更有益于理解。

Promise代表一個(gè)異步計(jì)算的最終結(jié)果。使用promise最基礎(chǔ)的方式是使用它的then方法,該方法會注冊兩個(gè)回調(diào)函數(shù),一個(gè)接收promise完成的最終值,一個(gè)接收promise被拒絕的原因。

Promises/A

你可能還會想問Promises/A是什么,和Promises/A+有什么區(qū)別。Promises/A+在Promises/A議案的基礎(chǔ)上,更清晰闡述了一些準(zhǔn)則,拓展覆蓋了一些事實(shí)上的行為規(guī)范,同時(shí)刪除了一些不足或者有問題的部分。

Promises/A+規(guī)范目前只關(guān)注如何提供一個(gè)可操作的then方法,而關(guān)于如何創(chuàng)建,決議promises是日后的工作。

術(shù)語

  1. promise: 指一個(gè)擁有符合規(guī)范的then方法的對象;
  2. thenable: 指一個(gè)定義了then方法的對象;
  3. 決議(resolve): 改變一個(gè)promise等待狀態(tài)至已完成或被拒絕狀態(tài), 一旦決議,不再可變;
  4. 值(value): 一個(gè)任意合法的JavaScript值,包括undefined,thenable對象,promise對象;
  5. exception/error: JavaScript引擎拋出的異常/錯(cuò)誤
  6. 拒絕原因(reject reason): 一個(gè)promise被拒絕的原因

Promise狀態(tài)

一個(gè)promise只可能處于三種狀態(tài)之一:

  • 等待(pending):初始狀態(tài);
  • 已完成(fulfilled):操作成功完成;
  • 被拒絕(rejected):操作失敗;

這三個(gè)狀態(tài)變更關(guān)系需滿足以下三個(gè)條件:

  • 處于等待(pending)狀態(tài)時(shí),可以轉(zhuǎn)變?yōu)橐淹瓿?fulfilled)或者被拒絕狀態(tài)(rejected);
  • 處于已完成狀態(tài)時(shí),狀態(tài)不可變,且需要有一個(gè)最終值;
  • 處于被拒絕狀態(tài)時(shí),狀態(tài)不可變,且需要有一個(gè)拒絕原因。

then方法

一個(gè)promise必須提供一個(gè)then方法,以供訪問其當(dāng)前狀態(tài),或最終值或拒絕原因。

參數(shù)

該方法接收兩個(gè)參數(shù),如promise.then(onFulfilled, onRejected):

  • 兩個(gè)參數(shù)均為可選,均有默認(rèn)值,若不傳入,則會使用默認(rèn)值;
  • 兩個(gè)參數(shù)必須是函數(shù),否則會被忽略,使用默認(rèn)函數(shù);
  • onFulfilled: 在promise已完成后調(diào)用且僅調(diào)用一次該方法,該方法接受promise最終值作參數(shù);
  • onRejected: 在promise被拒絕后調(diào)用且僅調(diào)用一次該方法,該方法接受promise拒絕原因作參數(shù);
  • 兩個(gè)函數(shù)都是異步事件的回調(diào),符合JavaScript事件循環(huán)處理流程

返回值

該方法必須返回一個(gè)promise:

  1. var promise2 = promise1.then(onFulfilled, onRejected); 
  2. // promise2依然是一個(gè)promise對象  

決議過程(resolution)

決議是一個(gè)抽象操作過程,該操作接受兩個(gè)輸入:一個(gè)promise和一個(gè)值,可以記為;[[resolve]](promise, x),如果x是一個(gè)thenable對象,則嘗試讓promise參數(shù)使用x的狀態(tài)值;否則,將使用x值完成傳入的promise,決議過程規(guī)則如下:

1.如果promise和x引用自同一對象,則使用一個(gè)TypeError原因拒絕此promise;

2.x為Promise,則promise直接使用x的狀態(tài);

3.x為對象或函數(shù):

  1. 獲取一個(gè)x.then的引用;
  2. 若獲取x.then時(shí)拋出異常e,使用該e作為原因拒絕promise;
  3. 否則將該引用賦值給then;
  4. 若then是一個(gè)函數(shù),就調(diào)用該函數(shù),其作用域?yàn)閤,并傳遞兩個(gè)回調(diào)函數(shù)參數(shù),第一個(gè)是resolvePromise,第二個(gè)是rejectPromise:
    1. 若調(diào)用了resolvePromise(y),則執(zhí)行resolve(promise, y);
    2. 若調(diào)用了rejectPrtomise(r),則使用原因r拒絕promise;
    3. 若多次調(diào)用,只會執(zhí)行第一次調(diào)用流程,后續(xù)調(diào)用將被忽略;
    4. 若調(diào)用then拋出異常e,則:
      1. 若promise已決議,即調(diào)用了resolvePromise或rejectPrtomise,則忽略此異常;
      2. 否則,使用原因e拒絕promise;

      5.若then不是函數(shù),則使用x值完成promise;

4.若x不是對象或函數(shù),則使用x完成promise。

自然,以上規(guī)則可能存在遞歸循環(huán)調(diào)用的情況,如一個(gè)promsie被一個(gè)循環(huán)的thenable對象鏈決議,此時(shí)自然是不行的,所以規(guī)范建議進(jìn)行檢測,是否存在遞歸調(diào)用,若存在,則以原因TypeError拒絕promise。

Promise

在ES6中,JavaScript已支持Promise,一些主流瀏覽器也已支持該P(yáng)romise功能,如Chrome,先來看一個(gè)Promsie使用實(shí)例:

  1. var promise = new Promise((resolve, reject) => { 
  2.         setTimeout(function() { 
  3.             resolve('完成'); 
  4.         }, 10); 
  5.     }); 
  6.     promise.then((msg) => { 
  7.         console.log('first messaeg: ' + msg); 
  8.     }) 
  9.     promise.then((msg) => { 
  10.         console.log('second messaeg: ' + msg); 
  11.     });  

輸出如下: 

 

 

 

構(gòu)造器

創(chuàng)建promise語法如下:

  1. new Promise(function(resolve, reject) {}); 
  • 參數(shù)

一個(gè)函數(shù),該函數(shù)接受兩個(gè)參數(shù):resolve函數(shù)和reject函數(shù);當(dāng)實(shí)例化Promise構(gòu)造函數(shù)時(shí),將立即調(diào)用該函數(shù),隨后返回一個(gè)Promise對象。通常,實(shí)例化時(shí),會初始一個(gè)異步任務(wù),在異步任務(wù)完成或失敗時(shí),調(diào)用resolve或reject函數(shù)來完成或拒絕返回的Promise對象。另外需要注意的是,若傳入的函數(shù)執(zhí)行拋出異常,那么這個(gè)promsie將被拒絕。

靜態(tài)方法

Promise.all(iterable)

all方法接受一個(gè)或多個(gè)promsie(以數(shù)組方式傳遞),返回一個(gè)新promise,該promise狀態(tài)取決于傳入的參數(shù)中的所有promsie的狀態(tài):

  1. 當(dāng)所有promise都完成是,返回的promise完成,其最終值為由所有完成promsie的最終值組成的數(shù)組;
  2. 當(dāng)某一promise被拒絕時(shí),則返回的promise被拒絕,其拒絕原因?yàn)榈谝粋€(gè)被拒絕promise的拒絕原因;
  1. var p1 = new Promise((resolve, reject) => { 
  2.        setTimeout(function(){ 
  3.            console.log('p1決議'); 
  4.            resolve('p1'); 
  5.        }, 10); 
  6.    }); 
  7.    var p2 = new Promise((resolve, reject) => { 
  8.        setTimeout(function(){ 
  9.            console.log('p2決議'); 
  10.            resolve('p2'); 
  11.        }, 10); 
  12.    }); 
  13.    Promise.all( [p1, p2] ) 
  14.    .then((msgs) => { 
  15.        // p1和p2完成并傳入最終值 
  16.        console.log(JSON.stringify(msgs)); 
  17.    }) 
  18.    .then((msg) => { 
  19.        console.log( msg ); 
  20.    });  

輸出如下: 

 

 

 

Promise.race(iterable)

race方法返回一個(gè)promise,只要傳入的諸多promise中的某一個(gè)完成或被拒絕,則該promise同樣完成或被拒絕,最終值或拒絕原因也與之相同。

Promise.resolve(x)

resolve方法返回一個(gè)已決議的Promsie對象:

  1. 若x是一個(gè)promise或thenable對象,則返回的promise對象狀態(tài)同x;
  2. 若x不是對象或函數(shù),則返回的promise對象以該值為完成最終值;
  3. 否則,詳細(xì)過程依然按前文Promsies/A+規(guī)范中提到的規(guī)則進(jìn)行。

該方法遵循Promise/A+決議規(guī)范。

Promsie.reject(reason)

返回一個(gè)使用傳入的原因拒絕的Promise對象。

實(shí)例方法

Promise.prototype.then(onFulfilled, onRejected)

該方法為promsie添加完成或拒絕處理器,將返回一個(gè)新的promise,該新promise接受傳入的處理器調(diào)用后的返回值進(jìn)行決議;若promise未被處理,如傳入的處理器不是函數(shù),則新promise維持原來promise的狀態(tài)。

我們通過兩個(gè)例子介紹then方法,首先看第一個(gè)實(shí)例:

  1. var promise = new Promise((resolve, reject) => { 
  2.        setTimeout(function() { 
  3.            resolve('完成'); 
  4.        }, 10); 
  5.    }); 
  6.    promise.then((msg) => { 
  7.        console.log('first messaeg: ' + msg); 
  8.    }).then((msg) => { 
  9.        console.log('second messaeg: ' + msg); 
  10.    });  

輸出如下: 

 

 

 

輸出兩行信息:我們發(fā)現(xiàn)第二個(gè)then方法接收到的最終值是undefined,為什么呢?看看第一個(gè)then方法調(diào)用后返回的promise狀態(tài)如下: 

 

 

 

如上圖,發(fā)現(xiàn)調(diào)用第一個(gè)then方法后,返回promise最終值為undefined,傳遞給第二個(gè)then的回調(diào),如果把上面的例子稍加改動(dòng):

  1. var promise = new Promise((resolve, reject) => { 
  2.         setTimeout(function() { 
  3.             resolve('完成'); 
  4.         }, 10); 
  5.     }); 
  6.     promise.then((msg) => { 
  7.         console.log('first messaeg: ' + msg); 
  8.         return msg + '第二次'
  9.     }).then((msg) => { 
  10.         console.log('second messaeg: ' + msg); 
  11.     });  

輸出如下: 

 

 

 

這次兩個(gè)then方法的回調(diào)都接收到了最終值,正如我們前文所說,'then'方法返回一個(gè)新promise,并且該新promise根據(jù)其傳入的回調(diào)執(zhí)行的返回值,進(jìn)行決議,而函數(shù)未明確return返回值時(shí),默認(rèn)返回的是undefined,這也是上面實(shí)例第二個(gè)then方法的回調(diào)接收undefined參數(shù)的原因。

這里使用了鏈?zhǔn)秸{(diào)用,我們需要明確:共產(chǎn)生三個(gè)promise,初始promise,兩個(gè)then方法分別返回一個(gè)promise;而第一個(gè)then方法返回的新promise是第二個(gè)then方法的主體,而不是初始promise。

Promise.prototype.catch(onRejected)

該方法為promise添加拒絕回調(diào)函數(shù),將返回一個(gè)新promise,該新promise根據(jù)回調(diào)函數(shù)執(zhí)行的返回值進(jìn)行決議;若promise決議為完成狀態(tài),則新promise根據(jù)其最終值進(jìn)行決議。

  1. var promise = new Promise((resolve, reject) => { 
  2.         setTimeout(() => { 
  3.             reject('failed'); 
  4.         }, 0); 
  5.     }); 
  6.  
  7.     var promise2 = promise.catch((reason) => { 
  8.         console.log(reason); 
  9.         return 'successed'
  10.     }); 
  11.     var promise3 = promise.catch((reason) => { 
  12.         console.log(reason); 
  13.     }); 
  14.     var promise4 = promise.catch((reason) => { 
  15.         console.log(reason); 
  16.         throw 'failed 2'
  17.     });  

輸出如下圖: 

 

 

 

如圖中所輸出內(nèi)容,我們需要明白以下幾點(diǎn):

  1. catch會為promise注冊拒絕回調(diào)函數(shù),一旦異步操作結(jié)束,調(diào)用了reject回調(diào)函數(shù),則依次執(zhí)行注冊的拒絕回調(diào);
  2. 另外有一點(diǎn)和then方法相似,catch方法返回的新promise將使用其回調(diào)函數(shù)執(zhí)行的返回值進(jìn)行決議,如promise2,promise3狀態(tài)均為完成(resolved),但是promise3最終值為undefined,而promise2最終值為successed,這是因?yàn)樵谡{(diào)用promise.catch方法時(shí),傳入的回調(diào)沒有顯式的設(shè)置返回值;
  3. 對于promise4,由于調(diào)用catch方法時(shí),回調(diào)中throw拋出異常,所以promise4狀態(tài)為拒絕(rejected),拒絕原因?yàn)閽伋龅漠惓?
  4. 特別需要注意的是這里一共有四個(gè)promise,一旦決議,它們之間都是獨(dú)立的,我們需要明白無論是then方法,還是catch方法,都會返回一個(gè)新promise,此新promise與初始promise相互獨(dú)立。

catch方法和then方法的第二個(gè)參數(shù)一樣,都是為promise注冊拒絕回調(diào)。

鏈?zhǔn)秸{(diào)用

和jQuery的鏈?zhǔn)秸{(diào)用一樣,Promise設(shè)計(jì)也支持鏈?zhǔn)秸{(diào)用,上一步的返回值作為下一步方法調(diào)用的主體:

  1. new Promise((resolve, reject) => { 
  2.        setTimeout(()=>{ 
  3.            resolve('success'); 
  4.        },0); 
  5.    }).then((msg) => { 
  6.        return 'second success'
  7.    }).then((msg) => { 
  8.        console.log(msg); 
  9.    });  

最后輸出:second success,初始化promise作為主體調(diào)用第一個(gè)then方法,返回完成狀態(tài)的新promise其最終值為second success,然后該新promise作為主體調(diào)用第二個(gè)then方法,該方法返回第三個(gè)promise,而且該promise最終值為undefined,若不清楚為什么,請回到關(guān)于Promise.prototype.then和Promise.prototype.catch的介紹。

錯(cuò)誤處理

我們前文提到了JavaScript異步回調(diào)中的異常是難以處理的,而Promise對異步異常和錯(cuò)誤的處理是比較方便的:

  1. var promise = new Promise((resolve, reject) => { 
  2.        test(); // 拋出異常 
  3.        resolve('success'); // 被忽略 
  4.    }); 
  5.    console.log(promise); 
  6.    promise.catch((reason) => { 
  7.        console.log(reason); 
  8.    });  

輸出如圖,執(zhí)行test拋出異常,導(dǎo)致promise被拒絕,拒絕原因即拋出的異常,然后執(zhí)行catch方法注冊的拒絕回調(diào): 

 

 

 

決議,完成與拒絕

目前為止,關(guān)于Promise是什么,我們應(yīng)該有了一定的認(rèn)識,這里,需要再次說明的是Promise的三個(gè)重要概念及其關(guān)系:決議(resolve),完成(fulfill),拒絕(reject)。

  1. 完成與拒絕是Promise可能處于的兩種狀態(tài);
  2. 決議是一個(gè)過程,是Promise由等待狀態(tài)變更為完成或拒絕狀態(tài)的一個(gè)過程;
  3. 靜態(tài)方法Promise.resolve描述的就是一個(gè)決議過程,而Promise構(gòu)造函數(shù),傳入的回調(diào)函數(shù)的兩個(gè)參數(shù):resolve和reject,一個(gè)是完成函數(shù),一個(gè)是拒絕函數(shù),這里令人疑惑的是為什么這里依然使用resolve而不是fulfill,我們通過一個(gè)例子解釋這個(gè)問題:
  1. var promise = new Promise((resolve, reject) => { 
  2.         resolve(Promise.reject('failed')); 
  3.     }); 
  4.     promise.then((msg) => { 
  5.         console.log('完成:' + msg); 
  6.     }, (reason) => { 
  7.         console.log('拒絕:' + reason); 
  8.     });  

輸出如圖: 

 

 

 

上例中,在創(chuàng)建一個(gè)Promise時(shí),給resolve函數(shù)傳遞的是一個(gè)拒絕Promise,此時(shí)我們發(fā)現(xiàn)promise狀態(tài)是rejected,所以這里第一個(gè)參數(shù)函數(shù)執(zhí)行,完成的是一個(gè)更接近決議的過程(可以參考前文講述的決議過程),所以命名為resolve是更合理的;而第二個(gè)參數(shù)函數(shù),則只是拒絕該promise:

  1. var promise = new Promise((resolve, reject) => { 
  2.        reject(Promise.resolve('success')); 
  3.    }); 
  4.    promise.then((msg) => { 
  5.        console.log('完成:' + msg); 
  6.    }, (reason) => { 
  7.        console.log('拒絕:' + reason); 
  8.    });  

reject函數(shù)并不會處理參數(shù),而只是直接將其當(dāng)做拒絕原因拒絕promise。

Promise實(shí)現(xiàn)

Promise是什么,怎么樣使用就介紹到此,另外一個(gè)問題是面試過程中經(jīng)常也會被提及的:如何實(shí)現(xiàn)一個(gè)Promise,當(dāng)然,限于篇幅,我們這里只講思路,不會長篇大論。

構(gòu)造函數(shù)

首先創(chuàng)建一個(gè)構(gòu)造函數(shù),供實(shí)例化創(chuàng)建promise,該構(gòu)造函數(shù)接受一個(gè)函數(shù)參數(shù),實(shí)例化時(shí),會立即調(diào)用該函數(shù),然后返回一個(gè)Promise對象:

  1. var MyPromise = (() => { 
  2.         var value = undefined; // 當(dāng)前Promise 
  3.         var tasks = []; // 完成回調(diào)隊(duì)列 
  4.         var rejectTasks = []; // 拒絕回調(diào)隊(duì)列 
  5.         var state = 'pending'; // Promise初始為等待態(tài) 
  6.  
  7.         // 輔助函數(shù),使異步回調(diào)下一輪事件循環(huán)執(zhí)行 
  8.         var nextTick = (callback) => { 
  9.             setTimeout(callback, 0); 
  10.         }; 
  11.  
  12.         // 輔助函數(shù),傳遞Promsie的狀態(tài)值 
  13.         var ref = (value) => { 
  14.             if (value && typeof value.then === 'function') { 
  15.                 // 若狀態(tài)值為thenable對象或Promise,直接返回 
  16.                 return value; 
  17.             } 
  18.             // 否則,將最終值傳遞給下一個(gè)then方法注冊的回調(diào)函數(shù) 
  19.             return { 
  20.                 thenfunction(callback) { 
  21.                     return ref(callback(value)); 
  22.                 } 
  23.             } 
  24.         }; 
  25.         var resolve = (val) => {}; 
  26.         var reject = (reason) => {}; 
  27.  
  28.         function MyPromise(func) { 
  29.             func(resolve.bind(this), reject.bind(this)); 
  30.         } 
  31.  
  32.         return MyPromise; 
  33.     });  

靜態(tài)方法

在實(shí)例化創(chuàng)建Promise時(shí),我們會將構(gòu)造函數(shù)的兩個(gè)靜態(tài)方法:resolve和reject傳入初始函數(shù),接下來需要實(shí)現(xiàn)這兩個(gè)函數(shù):

  1. var resolve = (val) => { 
  2.         if (tasks) { 
  3.             value = ref(val); 
  4.             state = 'resolved'; // 將狀態(tài)標(biāo)記為已完成 
  5.             // 依次執(zhí)行任務(wù)回調(diào) 
  6.             tasks.forEach((task) => { 
  7.                 value = nextTick((val) => {task[0](self.value);}); 
  8.             }); 
  9.             tasks = undefined; // 決議后狀態(tài)不可變 
  10.  
  11.             return this; 
  12.         } 
  13.     }; 
  14.     var reject = (reason) => { 
  15.         if (tasks) { 
  16.             value = ref(reason); 
  17.             state = 'rejected'; // 將狀態(tài)標(biāo)記為已完成 
  18.  
  19.             // 依次執(zhí)行任務(wù)回調(diào) 
  20.             tasks.forEach((task) => { 
  21.                 nextTick((reason) => {task[1](value);}); 
  22.             }); 
  23.             tasks = undefined; // 決議后狀態(tài)不可變 
  24.  
  25.             return this; 
  26.         } 
  27.     };  

還有另外兩個(gè)靜態(tài)方法,原理還是一樣,就不細(xì)說了。

實(shí)例方法

目前構(gòu)造函數(shù),和靜態(tài)方法完成和拒絕Promise都已經(jīng)實(shí)現(xiàn),接下來需要考慮的是Promise的實(shí)例方法和鏈?zhǔn)秸{(diào)用:

  1. MyPromise.prototype.then = (onFulfilled, onRejected) => { 
  2.        onFulfilled = onFulfilled || function(value) { 
  3.            // 默認(rèn)的完成回調(diào) 
  4.            return value; 
  5.        }; 
  6.        onRejected = onRejected || function(reason) { 
  7.            // 默認(rèn)的拒絕回調(diào) 
  8.            return reject(reason); 
  9.        }; 
  10.  
  11.        if (tasks) { 
  12.            // 未決議時(shí)加入隊(duì)列 
  13.             tasks.push(onFulfilled); 
  14.             rejectTasks.push(onRejected); 
  15.        } else { 
  16.            // 已決議,直接加入事件循環(huán)執(zhí)行 
  17.             nextTick(() => { 
  18.                 if (state === 'resolved') { 
  19.                     value.then(onFulfilled); 
  20.                 } else if (state === 'rejected') { 
  21.                     value.then(onRejected); 
  22.                 } 
  23.             }); 
  24.        } 
  25.  
  26.        return this; 
  27.    };  

實(shí)例

以上可以簡單實(shí)現(xiàn)Promise部分異步管理功能:

  1. var promise = new MyPromise((resolve, reject) => { 
  2.        setTimeout(() => { 
  3.            resolve('完成'); 
  4.        }, 0); 
  5.    }); 
  6.    promise.then((msg) => {console.log(msg);});  

本篇由回調(diào)函數(shù)起,介紹了回調(diào)處理異步任務(wù)的常見問題,然后介紹Promises/A+規(guī)范及Promise使用,最后就Promise實(shí)現(xiàn)做了簡單闡述(之后有機(jī)會會詳細(xì)實(shí)現(xiàn)一個(gè)Promise),花費(fèi)一周終于把基本知識點(diǎn)介紹完,下一篇將介紹JavaScript異步與生成器實(shí)現(xiàn)。

參考

  1. Promises/A+ specification
  2. JavaScript Promise 
責(zé)任編輯:龐桂玉 來源: 熊建剛的博客
相關(guān)推薦

2021-06-06 19:51:07

JavaScript異步編程

2018-11-29 08:00:20

JavaScript異步Promise

2017-07-13 12:12:19

前端JavaScript異步編程

2023-09-15 15:31:23

異步編程Promise

2016-10-25 16:04:49

GeneratorPromiseJavaScript

2019-12-09 15:20:09

JavascriptPromise前端

2024-09-02 14:12:56

2020-03-23 11:28:56

PythonJavaScript技術(shù)

2015-07-23 11:59:27

JavascriptPromise

2023-01-12 11:23:11

Promise異步編程

2025-03-07 07:20:00

JavaScript異步編程Promise

2013-03-08 09:33:25

JavaScript同步異步

2009-07-01 14:23:46

JavaScript異

2009-07-01 14:37:14

JavaScript異

2020-10-15 13:29:57

javascript

2020-03-29 08:27:05

Promise異步編程前端

2022-01-04 20:52:50

函數(shù)異步Promise

2025-02-12 12:00:00

前端try-catchJavaScrip

2011-02-24 12:53:51

.NET異步傳統(tǒng)

2018-07-12 15:40:23

前端JavaScripthtml
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號