什么是 Promise
一個(gè) Promise 對(duì)象代表一個(gè)目前還不可用,但是在未來(lái)的某個(gè)時(shí)間點(diǎn)可以被解析的值。它允許你以一種同步的方式編寫(xiě)異步代碼。例如,如果你想要使用 Promise API 異步調(diào)用一個(gè)遠(yuǎn)程的服務(wù)器,你需要?jiǎng)?chuàng)建一個(gè)代表數(shù)據(jù)將會(huì)在未來(lái)由 Web 服務(wù)返回的 Promise 對(duì)象。唯一的問(wèn)題是目前數(shù)據(jù)還不可用。當(dāng)請(qǐng)求完成并從服務(wù)器返回時(shí)數(shù)據(jù)將變?yōu)榭捎脭?shù)據(jù)。在此期間,Promise 對(duì)象將扮演一個(gè)真實(shí)數(shù)據(jù)的代理角色。接下來(lái),你可以在 Promise 對(duì)象上綁定一個(gè)回調(diào)函數(shù),一旦真實(shí)數(shù)據(jù)變得可用這個(gè)回調(diào)函數(shù)將會(huì)被調(diào)用。
Promise 對(duì)象曾經(jīng)以多種形式存在于許多語(yǔ)言中。
去除厄運(yùn)的回調(diào)金字塔(Pyramid of Doom)
Javascript 中最常見(jiàn)的反模式做法是回調(diào)內(nèi)部再嵌套回調(diào)。
引入 Promises 之后的代碼
Promises 將嵌套的 callback,改造成一系列的.then的連綴調(diào)用,去除了層層縮進(jìn)的糟糕代碼風(fēng)格。Promises 不是一種解決具體問(wèn)題的算法,而已一種更好的代碼組織模式。接受新的組織模式同時(shí),也逐漸以全新的視角來(lái)理解異步調(diào)用。
各個(gè)語(yǔ)言平臺(tái)都有相應(yīng)的 Promise 實(shí)現(xiàn)
下面我來(lái)相信了解一下 javascript 語(yǔ)言環(huán)境下各個(gè)規(guī)范的一些細(xì)節(jié)。
Promises/A 規(guī)范
promise 表示一個(gè)最終值,該值由一個(gè)操作完成時(shí)返回。
promise 有一個(gè) then 方法,then 方法可以接受 3 個(gè)函數(shù)作為參數(shù)。前兩個(gè)函數(shù)對(duì)應(yīng) promise 的兩種狀態(tài) fulfilled 和 rejected 的回調(diào)函數(shù)。第三個(gè)函數(shù)用于處理進(jìn)度信息(對(duì)進(jìn)度回調(diào)的支持是可選的)。
如果 promise 支持如下連個(gè)附加方法,稱之為可交互的 promise
獲得當(dāng)前 promise 最終值上的一個(gè)屬性,返回值是一個(gè)新的 promise。
調(diào)用當(dāng)然 promise 最終值上的一個(gè)方法,返回值也是一個(gè)新的promise。
Promises/B 規(guī)范
在 Promises/A 的基礎(chǔ)上,Promises/B 定義了一組 promise 模塊需要實(shí)現(xiàn)的 API
when(value, callback, errback_opt)
如果 value 不是一個(gè) promise ,那么下一事件循環(huán)callback會(huì)被調(diào)用,value 作為 callback 的傳入值。如果 value 是一個(gè) promise,promise 的狀態(tài)已經(jīng)完成或者變成完成時(shí),那么下一事件循環(huán) callback 會(huì)被調(diào)用,resolve 的值會(huì)被傳入 callback;promise 的狀態(tài)已經(jīng)失敗或者變成失敗時(shí),那么下一事件循環(huán) errback 會(huì)被調(diào)用,reason 會(huì)作為失敗的理由傳入 errback。
asap(value, callback, errback_opt)
與 when 最大的區(qū)別,如果 value 不是一個(gè) promise,會(huì)被立即執(zhí)行,不會(huì)等到下一事件循環(huán)。
enqueue(task Function)
盡可能快地在接下來(lái)的事件循環(huán)調(diào)用 task 方法。
get(object, name)
返回一個(gè)獲得對(duì)象屬性的 promise。
post(object, name, args)
返回一個(gè)調(diào)用對(duì)象方法的 promise。
put(object, name, value)
返回一個(gè)修改對(duì)象屬性的 promise。
del(object, name)
返回一個(gè)刪除對(duì)象屬性的 promise。
makePromise(descriptor Object, fallback Function)
返回一個(gè) promise 對(duì)象,該對(duì)象必須是一個(gè)可調(diào)用的函數(shù),也可能是可被實(shí)例化的構(gòu)造函數(shù)。
上面每一個(gè)注冊(cè)的 handle 都返回一個(gè) resolved value或者 promise。
defer()
返回一個(gè)對(duì)象,該對(duì)象包含一個(gè) resolve(value) 方法和一個(gè) promise 屬性。
當(dāng) resolve(value) 方法被第一次調(diào)用時(shí),promise 屬性的狀態(tài)變成 完成,所有之前或之后觀察該 promise 的 promise 的狀態(tài)都被轉(zhuǎn)變成 完成。value 參數(shù)如果不是一個(gè) promise ,會(huì)被包裝成一個(gè) promise 的 ref。resolve 方法會(huì)忽略之后的所有調(diào)用。
reject(reason String)
返回一個(gè)被標(biāo)記為 失敗 的 promise。
一個(gè)失敗的 promise 上被調(diào)用 when(message) 方法時(shí),會(huì)采用如下兩種方法之一
1. 如果存在 errback,errback 會(huì)以 reason 作為參數(shù)被調(diào)用。when方法會(huì)將 errback 的返回值返回。
2. 如果不存在 errback,when 方法返回一個(gè)新的 reject 狀態(tài)的promise 對(duì)象,以同一 reason 作為參數(shù)。
ref(value)
如果 value 是 promise 對(duì)象,返回 value 本身。否則,返回一個(gè)resolved 的 promise,攜帶如下 handle。
1. when(errback),忽略 errback,返回 resolved 值
2. get(name),返回 resolved 值的對(duì)應(yīng)屬性。
3. put(name, value) ,設(shè)置 resolved 值的對(duì)應(yīng)屬性。
4. del(name),刪除 resolved 值的對(duì)應(yīng)屬性。
5. post(name, args), 調(diào)用 resolved 值的對(duì)應(yīng)方法。
6. 其他所有的調(diào)用都返回一個(gè) reject,并攜帶 "Promise does not handle NAME" 的理由。
isPromise(value) Boolean
判斷一個(gè)對(duì)象是否是 promise
method(name String)
獲得一個(gè)返回 name 對(duì)應(yīng)方法的 promise。返回值是 "get", "put", "del" 和 "post" 對(duì)應(yīng)的方法,但是會(huì)在下一事件循環(huán)返回。
Promises/D 規(guī)范
為了增加不同 promise 實(shí)現(xiàn)之間的可互操作性,Promises/D 規(guī)范對(duì)promise 對(duì)象和 Promises/B 規(guī)范做了進(jìn)一步的約定。以達(dá)到鴨子類型的效果(Duck-type Promise)。
簡(jiǎn)單來(lái)說(shuō)Promises/D 規(guī)范,做了兩件事情,
1、如何判斷一個(gè)對(duì)象是 Promise 類型。
2、對(duì) Promises/B 規(guī)范進(jìn)行細(xì)節(jié)補(bǔ)充。
甄別一個(gè) Promise 對(duì)象
Promise 對(duì)象必須是實(shí)現(xiàn) promiseSend 方法。
1. 在 promise 庫(kù)上下文中,如果對(duì)象包含 promiseSend 方法就可以甄別為promise 對(duì)象
2. promiseSend 方法必須接受一個(gè)操作名稱,作為第一個(gè)參數(shù)
3. 操作名稱是一個(gè)可擴(kuò)展的集合,下面是一些保留名稱
1. when,此時(shí)第三個(gè)參數(shù)必須是 rejection 回調(diào)。
1. rejection回調(diào)必須接受一個(gè) rejection 原因(可以是任何值)作為第一個(gè)參數(shù)
2. get,此時(shí)第三個(gè)參數(shù)為屬性名(字符串類型)
3. put,此時(shí)第三個(gè)參數(shù)為屬性名(字符串類型),第四個(gè)參數(shù)為新屬性值。
4. del,此時(shí)第三個(gè)參數(shù)為屬性名
5. post,此時(shí)第三個(gè)參數(shù)為方法的屬性名,接下來(lái)的變參為方法的調(diào)用參數(shù)
6. isDef
4. promiseSend方法的第二個(gè)參數(shù)為 resolver 方法
5. promiseSend方法可能接受變參
6. promiseSend方法必須返回undefined
對(duì) Promises/B 規(guī)范的補(bǔ)充
Promises/D 規(guī)范中對(duì) Promises/B 規(guī)范中定義的ref、reject、def、defer方法做了進(jìn)一步細(xì)致的約束,此處略去這些細(xì)節(jié)。
Promises/A+ 規(guī)范
前面提到的 Promises/A/B/D 規(guī)范都是有CommonJS組織提出的,Promises/A+是有一個(gè)自稱為Promises/A+ 組織發(fā)布的,該規(guī)范是以Promises/A作為基礎(chǔ)進(jìn)行補(bǔ)充和修訂,旨在提高promise實(shí)現(xiàn)之間的可互操作性。
Promises/A+ 對(duì).then方法進(jìn)行細(xì)致的補(bǔ)充,定義了細(xì)致的Promise Resolution Procedure流程,并且將.then方法作為promise的對(duì)象甄別方法。
此外,Promises/A+ 還提供了兼容性測(cè)試工具,以確定各個(gè)實(shí)現(xiàn)的兼容性。
實(shí)現(xiàn)一個(gè)迷你版本的Promise
上面扯了這么多規(guī)范,現(xiàn)在我們看看如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單而短小的Promise。
1、狀態(tài)機(jī)
2、狀態(tài)變遷
僅支持兩種狀態(tài)變遷,fulfill和reject
fulfill和reject方法較為底層,通常更高級(jí)的resolve方法開(kāi)放給外部。
resolve方法可以接受一個(gè)普通值或者另一個(gè)promise作為參數(shù),如果接受一個(gè)promise作為參數(shù),等待其完成。promise不允許被另一個(gè)promise fulfill,所以需要開(kāi)放resolve方法。resolve方法依賴一些幫助方法定義如下:
這里resolve和doResolve之間的遞歸很巧妙,用來(lái)處理promise的層層嵌套(promise的value是一個(gè)promise)。
構(gòu)造器
.done方法
.then方法
$.promise
jQuery 1.8 之前的版本,jQuery的 then 方法只是一種可以同時(shí)調(diào)用 done 、fail 和 progress 這三種回調(diào)的速寫(xiě)方法,而 Promises/A 規(guī)范的 then 在行為上更像是 jQuery 的 pipe。 jQuery 1.8 修正了這個(gè)問(wèn)題,使 then 成為 pipe 的同義詞。不過(guò),由于向后兼容的問(wèn)題,jQuery 的 Promise 再如何對(duì) Promises/A 示好也不太會(huì)招人待見(jiàn)。
此外,在 Promises/A 規(guī)范中,由 then 方法生成的 Promise 對(duì)象是已執(zhí)行還是已拒絕,取決于由 then 方法調(diào)用的那個(gè)回調(diào)是返回值還是拋出錯(cuò)誤。在 JQuery 的 Promise 對(duì)象的回調(diào)中拋出錯(cuò)誤是個(gè)糟糕的主意,因?yàn)殄e(cuò)誤不會(huì)被捕獲。
小結(jié)
最后一個(gè)例子揭示了,實(shí)現(xiàn) Promise 的關(guān)鍵是實(shí)現(xiàn)好 doResolve 方法,在完事以后觸發(fā)回調(diào)。而為了保證異步 setTimeout(fun, 0); 是關(guān)鍵一步。
Promise 一直用得蠻順手的,其很好的優(yōu)化了 NodeJS 異步處理時(shí)的代碼結(jié)構(gòu)。但是對(duì)于其工作原理卻有些懵懂和好奇,于是花了些精力查閱并翻譯了Promise 的規(guī)范,以充分的理解 Promise 的細(xì)節(jié)。
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com