Promise摘抄與學(xué)習(xí)

//本文內(nèi)容起初摘抄于 阮一峰 作者的譯文,用于記錄和學(xué)習(xí),建議觀者移步于原文

概念:

? ? ?所謂的Promise,簡單來說就是一個容器,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果。從語法上說,Promise是一個對象,從它可以獲取異步操作的消息。

特點:

? ? ? 1、Promise對象有三種狀態(tài):Pending(進(jìn)行中,adj. 未決定的;行將發(fā)生的)、Resolved(已完成,又稱為Fulfilled)和Rejected(已失敗),只有異步操作的結(jié)果才能決定其所處的狀態(tài)。

? ? ? 2、Promise的狀態(tài)一旦改變,就不會再變,變化只有兩種:從Pending變?yōu)镽esolved或從Pending變?yōu)镽ejected。狀態(tài)改變后就會凝固,意思為如果狀態(tài)已經(jīng)發(fā)生變化,再對Promise對象添加回調(diào)函數(shù),也會立即得到結(jié)果。這一點與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監(jiān)聽,是得不到結(jié)果的。

缺點:Promise的缺點包括,無法取消Promise,一旦新建Promise則會立即執(zhí)行,無法中途取消。其次,如果不設(shè)置回調(diào)函數(shù),Promise的內(nèi)部拋出的錯誤就不會反應(yīng)到外部。第三,當(dāng)處于Pending狀態(tài)時,無法得知目前具體進(jìn)展的階段。

如果某些事件不斷反復(fù)發(fā)生,一般來說,使用stream模式是比部署Promise更好的選擇。

使用方法:

//Promise構(gòu)造函數(shù)接受一個函數(shù)作為參數(shù),該函數(shù)的兩個參數(shù)分別為resolve和reject,他們是兩個函數(shù),由Javascript引擎提供,不用自己部署;

var promise = new Promise(function(resolve,reject){

//...some code

if(/*異步操作成功*/){resolve(value);

}else{ ?reject(error);?};

});

//創(chuàng)建實例后通過promise.then方法添加回調(diào)函數(shù)

promise.then(function(value){

/*success*/},function(error){

/*failure*/});

示例:

異步加載圖片:

function loadImageAsync(url){

? ? ? return new Promise(function(resolve,reject){

? ? ? ? ? var image = new Image();

? ? ? ? ? image.onload = function(){resolve(image)};

? ? ? ? ? image.onerror = function(){reject(new Error("could not load image at"+url))};

? ? ? ? ? image.src = url;

? ? ? });

};

使用Promise實現(xiàn)Ajax操作,對XMLHttpRequest對象進(jìn)行封裝,用于發(fā)出一個針對JSON數(shù)據(jù)的HTTP請求:

var getJson = function(url){

var promise = new Promise(function(resolve,reject){

var client = new XMLHttpRequest();client.open("GET",url);

client.onreadystatechange = handler;client.responseType = "json";

client.setRequestHeader("Accept","application/json");client.send();

function handler(){

if(this.readyState !==4){return};

if(this.status === 200){resolve(this.response);}else{reject(new Error(this.statusText))};

};});};

getJson("/posts.json").then(function(json){

console.log('contents:'+json)},function(error){console.error("Error",error)});

注意:resolve的參數(shù)除了正常值意外,還可能是另一個Promise實例,如下:

var p1 = new Promise(function(resolve,reject){});

var p2 = new Promise(function(resolve,reject){/*code*/ resolve(p1)});

以上,p1的狀態(tài)會傳遞給p2,意指p1的狀態(tài)決定p2的狀態(tài)。如果p1的狀態(tài)是Pending,那么p2的回調(diào)函數(shù)會等待p1的狀態(tài)改變;如果p1的狀態(tài)已經(jīng)是Resolved或者rejected,那么p2的回調(diào)函數(shù)將會立即執(zhí)行。

方法講解:

Promise.prototype.then();

Promise實例具有then方法,也就是說,then方法是定義在原型對象Promise.prototype上的。它的作用是為Promise實例添加狀態(tài)改變時的回調(diào)函數(shù)。前面說過,then方法的第一個參數(shù)是Resolved狀態(tài)的回調(diào)函數(shù),第二個參數(shù)(可選)是Rejected狀態(tài)的回調(diào)函數(shù)。

then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。因此可以采用鏈?zhǔn)綄懛?,即then方法后面再調(diào)用另一個then方法。

getJSON("/posts.json").then(function(json){returnjson.post;}).then(function(post){// ...});

上面的代碼使用then方法,依次指定了兩個回調(diào)函數(shù)。第一個回調(diào)函數(shù)完成以后,會將返回結(jié)果作為參數(shù),傳入第二個回調(diào)函數(shù)。

采用鏈?zhǔn)降膖hen,可以指定一組按照次序調(diào)用的回調(diào)函數(shù)。這時,前一個回調(diào)函數(shù),有可能返回的還是一個Promise對象(即有異步操作),這時后一個回調(diào)函數(shù),就會等待該P(yáng)romise對象的狀態(tài)發(fā)生變化,才會被調(diào)用。

Promise.prototype.catch();

Promise.prototype.catch方法是.then(null, rejection)的別名,用于指定發(fā)生錯誤時的回調(diào)函數(shù)。

getJSON("/posts.json").then(function(posts){// ...}).catch(function(error){// 處理 getJSON 和 前一個回調(diào)函數(shù)運(yùn)行時發(fā)生的錯誤console.log('發(fā)生錯誤!',error);});

上面代碼中,getJSON方法返回一個Promise對象,如果該對象狀態(tài)變?yōu)镽esolved,則會調(diào)用then方法指定的回調(diào)函數(shù);如果異步操作拋出錯誤,狀態(tài)就會變?yōu)镽ejected,就會調(diào)用catch方法指定的回調(diào)函數(shù),處理這個錯誤。另外,then方法指定的回調(diào)函數(shù),如果運(yùn)行中拋出錯誤,也會被catch方法捕獲。

reject方法的作用,等同于拋出錯誤,但如果Promise狀態(tài)已經(jīng)變成Resolved,再拋出錯誤是無效的。

Promise對象的錯誤具有“冒泡”性質(zhì),會一直向后傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個catch語句捕獲。

getJSON("/post/1.json").then(function(post){returngetJSON(post.commentURL);}).then(function(comments){// some code}).catch(function(error){// 處理前面三個Promise產(chǎn)生的錯誤});

上面代碼中,一共有三個Promise對象:一個由getJSON產(chǎn)生,兩個由then產(chǎn)生。它們之中任何一個拋出的錯誤,都會被最后一個catch捕獲。

一般來說,不要在then方法里面定義Reject狀態(tài)的回調(diào)函數(shù)(即then的第二個參數(shù)),總是使用catch方法。

跟傳統(tǒng)的try/catch代碼塊不同的是,如果沒有使用catch方法指定錯誤處理的回調(diào)函數(shù),Promise對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應(yīng)。

Promise.resolve().catch(function(error){console.log('oh no',error);}).then(function(){console.log('carry on');});

需要注意的是,如上:catch方法返回的還是一個Promise對象,因此后面還可以接著調(diào)用then方法,如果沒有報錯,則會跳過catch方法。

Promise.all();

用于將多個Promise實例,包裝成一個新的Promise實例:var p = Promise.all([p1,p2,p3]);

上面代碼中,Promise.all方法接受一個數(shù)組作為參數(shù),p1、p2、p3都是Promise對象的實例,如果不是,就會先調(diào)用下面講到的Promise.resolve方法,將參數(shù)轉(zhuǎn)為Promise實例,再進(jìn)一步處理。(Promise.all方法的參數(shù)可以不是數(shù)組,但必須具有Iterator接口,且返回的每個成員都是Promise實例。)

p的狀態(tài)由p1、p2、p3決定,分成兩種情況。

(1)只有p1、p2、p3的狀態(tài)都變成fulfilled,p的狀態(tài)才會變成fulfilled,此時p1、p2、p3的返回值組成一個數(shù)組,傳遞給p的回調(diào)函數(shù)。

(2)只要p1、p2、p3之中有一個被rejected,p的狀態(tài)就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調(diào)函數(shù)。

下面是一個具體的例子。

// 生成一個Promise對象的數(shù)組varpromises=[2,3,5,7,11,13].map(function(id){returngetJSON("/post/"+id+".json");});Promise.all(promises).then(function(posts){// ...}).catch(function(reason){// ...});

上面代碼中,promises是包含6個Promise實例的數(shù)組,只有這6個實例的狀態(tài)都變成fulfilled,或者其中有一個變?yōu)閞ejected,才會調(diào)用Promise.all方法后面的回調(diào)函數(shù)。

Promise.race()

Promise.race方法同樣是將多個Promise實例,包裝成一個新的Promise實例。

varp=Promise.race([p1,p2,p3]);

上面代碼中,只要p1、p2、p3之中有一個實例率先改變狀態(tài),p的狀態(tài)就跟著改變。那個率先改變的Promise實例的返回值,就傳遞給p的回調(diào)函數(shù)。

Promise.race方法的參數(shù)與Promise.all方法一樣,如果不是Promise實例,就會先調(diào)用下面講到的Promise.resolve方法,將參數(shù)轉(zhuǎn)為Promise實例,再進(jìn)一步處理。

下面是一個例子,如果指定時間內(nèi)沒有獲得結(jié)果,就將Promise的狀態(tài)變?yōu)閞eject,否則變?yōu)閞esolve。

varp=Promise.race([fetch('/resource-that-may-take-a-while'),newPromise(function(resolve,reject){setTimeout(()=>reject(newError('request timeout')),5000)})])p.then(response=>console.log(response))p.catch(error=>console.log(error))

上面代碼中,如果5秒之內(nèi)fetch方法無法返回結(jié)果,變量p的狀態(tài)就會變?yōu)閞ejected,從而觸發(fā)catch方法指定的回調(diào)函數(shù)。

Promise.resolve

有時需要將現(xiàn)有對象轉(zhuǎn)為Promise對象,Promise.resolve方法就起到這個作用。

varjsPromise=Promise.resolve($.ajax('/whatever.json'));

上面代碼將jQuery生成的deferred對象,轉(zhuǎn)為一個新的Promise對象。

Promise.resolve等價于下面的寫法。

Promise.resolve('foo')// 等價于newPromise(resolve=>resolve('foo'))

Promise.resolve方法的參數(shù)分成四種情況。

(1)參數(shù)是一個Promise實例

如果參數(shù)是Promise實例,那么Promise.resolve將不做任何修改、原封不動地返回這個實例。

(2)參數(shù)是一個thenable對象

thenable對象指的是具有then方法的對象,比如下面這個對象。

letthenable={then:function(resolve,reject){resolve(42);}};

Promise.resolve方法會將這個對象轉(zhuǎn)為Promise對象,然后就立即執(zhí)行thenable對象的then方法。

letthenable={then:function(resolve,reject){resolve(42);}};letp1=Promise.resolve(thenable);p1.then(function(value){console.log(value);// 42});

上面代碼中,thenable對象的then方法執(zhí)行后,對象p1的狀態(tài)就變?yōu)閞esolved,從而立即執(zhí)行最后那個then方法指定的回調(diào)函數(shù),輸出42。

(3)參數(shù)不是具有then方法的對象,或根本就不是對象

如果參數(shù)是一個原始值,或者是一個不具有then方法的對象,則Promise.resolve方法返回一個新的Promise對象,狀態(tài)為Resolved。

varp=Promise.resolve('Hello');p.then(function(s){console.log(s)});// Hello

上面代碼生成一個新的Promise對象的實例p。由于字符串Hello不屬于異步操作(判斷方法是它不是具有then方法的對象),返回Promise實例的狀態(tài)從一生成就是Resolved,所以回調(diào)函數(shù)會立即執(zhí)行。Promise.resolve方法的參數(shù),會同時傳給回調(diào)函數(shù)。

(4)不帶有任何參數(shù)

Promise.resolve方法允許調(diào)用時不帶參數(shù),直接返回一個Resolved狀態(tài)的Promise對象。

所以,如果希望得到一個Promise對象,比較方便的方法就是直接調(diào)用Promise.resolve方法。

varp=Promise.resolve();p.then(function(){// ...});

上面代碼的變量p就是一個Promise對象。

需要注意的是,立即resolve的Promise對象,是在本輪“事件循環(huán)”(event loop)的結(jié)束時,而不是在下一輪“事件循環(huán)”的開始時。

setTimeout(function(){console.log('three');},0);Promise.resolve().then(function(){console.log('two');});console.log('one');// one// two// three

上面代碼中,setTimeout(fn, 0)在下一輪“事件循環(huán)”開始時執(zhí)行,Promise.resolve()在本輪“事件循環(huán)”結(jié)束時執(zhí)行,console.log(’one‘)則是立即執(zhí)行,因此最先輸出。

Promise.reject()

Promise.reject(reason)方法也會返回一個新的Promise實例,該實例的狀態(tài)為rejected。它的參數(shù)用法與Promise.resolve方法完全一致。

varp=Promise.reject('出錯了');// 等同于varp=newPromise((resolve,reject)=>reject('出錯了'))p.then(null,function(s){console.log(s)});// 出錯了

上面代碼生成一個Promise對象的實例p,狀態(tài)為rejected,回調(diào)函數(shù)會立即執(zhí)行。

另兩個附加方法:

done()

Promise對象的回調(diào)鏈,不管以then方法或catch方法結(jié)尾,要是最后一個方法拋出錯誤,都有可能無法捕捉到(因為Promise內(nèi)部的錯誤不會冒泡到全局)。因此,我們可以提供一個done方法,總是處于回調(diào)鏈的尾端,保證拋出任何可能出現(xiàn)的錯誤。

asyncFunc().then(f1).catch(r1).then(f2).done();

它的實現(xiàn)代碼相當(dāng)簡單。

Promise.prototype.done=function(onFulfilled,onRejected){this.then(onFulfilled,onRejected).catch(function(reason){// 拋出一個全局錯誤setTimeout(()=>{throwreason},0);});};

從上面代碼可見,done方法的使用,可以像then方法那樣用,提供Fulfilled和Rejected狀態(tài)的回調(diào)函數(shù),也可以不提供任何參數(shù)。但不管怎樣,done都會捕捉到任何可能出現(xiàn)的錯誤,并向全局拋出。

finally();

finally方法用于指定不管Promise對象最后狀態(tài)如何,都會執(zhí)行的操作。它與done方法的最大區(qū)別,它接受一個普通的回調(diào)函數(shù)作為參數(shù),該函數(shù)不管怎樣都必須執(zhí)行。

下面是一個例子,服務(wù)器使用Promise處理請求,然后使用finally方法關(guān)掉服務(wù)器。

server.listen(0).then(function(){// run test}).finally(server.stop);

它的實現(xiàn)也很簡單。

Promise.prototype.finally=function(callback){letP=this.constructor;returnthis.then(value=>P.resolve(callback()).then(()=>value),reason=>P.resolve(callback()).then(()=>{throwreason}));};

上面代碼中,不管前面的Promise是fulfilled還是rejected,都會執(zhí)行回調(diào)函數(shù)callback。

應(yīng)用

加載圖片

我們可以將圖片的加載寫成一個Promise,一旦加載完成,Promise的狀態(tài)就發(fā)生變化。

const preloadImage=function(path){returnnewPromise(function(resolve,reject){varimage=newImage();image.onload=resolve;image.onerror=reject;image.src=path;});};

Generator函數(shù)與Promise的結(jié)合

使用Generator函數(shù)管理流程,遇到異步操作的時候,通常返回一個Promise對象。

functiongetFoo(){returnnewPromise(function(resolve,reject){resolve('foo');});}varg=function*(){try{varfoo=yieldgetFoo();console.log(foo);}catch(e){console.log(e);}};functionrun(generator){varit=generator();functiongo(result){if(result.done)returnresult.value;returnresult.value.then(function(value){returngo(it.next(value));},function(error){returngo(it.throw(error));});}go(it.next());}run(g);

上面代碼的Generator函數(shù)g之中,有一個異步操作getFoo,它返回的就是一個Promise對象。函數(shù)run用來處理這個Promise對象,并調(diào)用下一個next方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文適用的讀者 本文寫給有一定Promise使用經(jīng)驗的人,如果你還沒有使用過Promise,這篇文章可能不適合你,...
    HZ充電大喵閱讀 7,450評論 6 19
  • Promiese 簡單說就是一個容器,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果,語法上說,Pr...
    雨飛飛雨閱讀 3,485評論 0 19
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點點福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 11,120評論 26 95
  • 特點 Promise能將回調(diào)分離出來,在異步操作執(zhí)行之后,用鏈?zhǔn)椒椒▓?zhí)行回調(diào),雖然es5用封裝函數(shù)也能實現(xiàn),但是如...
    一二三kkxx閱讀 714評論 0 1
  • 蝕刻在大腦蜘蛛網(wǎng)般幽深回路的,是各種各樣的陳年物件和一無用處的車庫甩賣品。但偶爾回去看看也挺有趣的,回到記憶深處、...
    夏目清然閱讀 453評論 3 1

友情鏈接更多精彩內(nèi)容