摘要:前兩個(gè)函數(shù)對(duì)應(yīng)的兩種狀態(tài)和的回調(diào)函數(shù)。返回值是和對(duì)應(yīng)的方法,但是會(huì)在下一事件循環(huán)返回。此外,在規(guī)范中,由方法生成的對(duì)象是已執(zhí)行還是已拒絕,取決于由方法調(diào)用的那個(gè)回調(diào)是返回值還是拋出錯(cuò)誤。但是對(duì)于其工作原理卻有些懵懂和好奇。
原文: https://blog.coding.net/blog/how-do-promises-work
什么是 PromiseJavascript 采用回調(diào)函數(shù)(callback)來處理異步編程。從同步編程到異步回調(diào)編程有一個(gè)適應(yīng)的過程,但是如果出現(xiàn)多層回調(diào)嵌套,也就是我們常說的厄運(yùn)的回調(diào)金字塔(Pyramid of Doom),絕對(duì)是一種糟糕的編程體驗(yàn)。于是便有了 CommonJS 的 Promises/A 規(guī)范,用于解決回調(diào)金字塔問題。本文先介紹 Promises 相關(guān)規(guī)范,然后再通過解讀一個(gè)迷你的 Promises 以加深理解。
一個(gè) Promise 對(duì)象代表一個(gè)目前還不可用,但是在未來的某個(gè)時(shí)間點(diǎn)可以被解析的值。它允許你以一種同步的方式編寫異步代碼。例如,如果你想要使用 Promise API 異步調(diào)用一個(gè)遠(yuǎn)程的服務(wù)器,你需要?jiǎng)?chuàng)建一個(gè)代表數(shù)據(jù)將會(huì)在未來由 Web 服務(wù)返回的 Promise 對(duì)象。唯一的問題是目前數(shù)據(jù)還不可用。當(dāng)請(qǐng)求完成并從服務(wù)器返回時(shí)數(shù)據(jù)將變?yōu)榭捎脭?shù)據(jù)。在此期間, Promise 對(duì)象將扮演一個(gè)真實(shí)數(shù)據(jù)的代理角色。接下來,你可以在 Promise 對(duì)象上綁定一個(gè)回調(diào)函數(shù),一旦真實(shí)數(shù)據(jù)變得可用這個(gè)回調(diào)函數(shù)將會(huì)被調(diào)用。
Promise 對(duì)象曾經(jīng)以多種形式存在于許多語言中。
去除厄運(yùn)的回調(diào)金字塔(Pyramid of Doom)Javascript 中最常見的反模式做法是回調(diào)內(nèi)部再嵌套回調(diào)。
// 回調(diào)金字塔 asyncOperation(function(data){ // 處理 `data` anotherAsync(function(data2){ // 處理 `data2` yetAnotherAsync(function(){ // 完成 }); }); });
引入 Promises 之后的代碼
promiseSomething() .then(function(data){ // 處理 `data` return anotherAsync(); }) .then(function(data2){ // 處理 `data2` return yetAnotherAsync(); }) .then(function(){ // 完成 });
Promises 將嵌套的 callback ,改造成一系列的.then的連綴調(diào)用,去除了層層縮進(jìn)的糟糕代碼風(fēng)格。 Promises 不是一種解決具體問題的算法,而已一種更好的代碼組織模式。接受新的組織模式同時(shí),也逐漸以全新的視角來理解異步調(diào)用。
各個(gè)語言平臺(tái)都有相應(yīng)的 Promise 實(shí)現(xiàn)
Java"s java.util.concurrent.Future
Python"s Twisted deferreds and PEP-3148 futures
F#"s Async
.Net"s Task
C++ 11"s std::future
Dart"s Future
Javascript"s Promises/A/B/D/A+
下面我來相信了解一下 javascript 語言環(huán)境下各個(gè)規(guī)范的一些細(xì)節(jié)。
Promises/A 規(guī)范promise 表示一個(gè)最終值,該值由一個(gè)操作完成時(shí)返回。
promise 有三種狀態(tài):未完成 (unfulfilled),完成 (fulfilled) 和失敗 (failed)。
promise 的狀態(tài)只能由未完成轉(zhuǎn)換成完成,或者未完成轉(zhuǎn)換成失敗 。
promise 的狀態(tài)轉(zhuǎn)換只發(fā)生一次。
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)的支持是可選的)。
promiseSomething().then(function(fulfilled){ //當(dāng) promise 狀態(tài)變成 fulfilled 時(shí),調(diào)用此函數(shù) },function(rejected){ //當(dāng) promise 狀態(tài)變成 rejected 時(shí),調(diào)用此函數(shù) },function(progress){ //當(dāng)返回進(jìn)度信息時(shí),調(diào)用此函數(shù) });
如果 promise 支持如下連個(gè)附加方法,稱之為可交互的 promise
get(propertyName):獲得當(dāng)前 promise 最終值上的一個(gè)屬性,返回值是一個(gè)新的 promise
call(functionName, arg1, arg2, ...):調(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)
盡可能快地在接下來的事件循環(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è)參數(shù)接受一個(gè)描述對(duì)象,該對(duì)象結(jié)構(gòu)如下
{ "when": function(errback){...}, "get": function(name){...}, "put": function(name, value){...}, "post": function(name, args){...}, "del": function(name){...}, }
上面每一個(gè)注冊(cè)的 handle 都返回一個(gè) resolved value 或者 promise 。
第二個(gè)參數(shù)接受一個(gè) fallback(message,...args) 函數(shù),當(dāng)沒有 promise 對(duì)象沒有找到對(duì)應(yīng)的 handle 時(shí)該函數(shù)會(huì)被觸發(fā),返回一個(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ì)采用如下兩種方法之一
如果存在 errback , errback 會(huì)以 reason 作為參數(shù)被調(diào)用。 when 方法會(huì)將 errback 的返回值返回。
如果不存在 errback , when 方法返回一個(gè)新的 reject 狀態(tài)的 promise 對(duì)象,以同一 reason 作為參數(shù)。
ref(value)
如果 value 是 promise 對(duì)象,返回 value 本身。否則,返回一個(gè) resolved 的 promise ,攜帶如下 handle 。
when(errback),忽略 errback ,返回 resolved 值
get(name),返回 resolved 值的對(duì)應(yīng)屬性。
put(name, value) ,設(shè)置 resolved 值的對(duì)應(yīng)屬性。
del(name),刪除 resolved 值的對(duì)應(yīng)屬性。
post(name, args), 調(diào)用 resolved 值的對(duì)應(yīng)方法。
其他所有的調(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)返回。
為了增加不同 promise 實(shí)現(xiàn)之間的可互操作性, Promises/D 規(guī)范對(duì) promise 對(duì)象和 Promises/B 規(guī)范做了進(jìn)一步的約定。以達(dá)到鴨子類型的效果( Duck-type Promise )。
簡單來說 Promises/D 規(guī)范,做了兩件事情,
如何判斷一個(gè)對(duì)象是 Promise 類型。
對(duì) Promises/B 規(guī)范進(jìn)行細(xì)節(jié)補(bǔ)充。
甄別一個(gè) Promise 對(duì)象Promise 對(duì)象必須是實(shí)現(xiàn) promiseSend 方法。
在 promise 庫上下文中,如果對(duì)象包含 promiseSend 方法就可以甄別為 promise 對(duì)象
promiseSend 方法必須接受一個(gè)操作名稱,作為第一個(gè)參數(shù)
操作名稱是一個(gè)可擴(kuò)展的集合,下面是一些保留名稱
when 此時(shí)第三個(gè)參數(shù)必須是 rejection 回調(diào),rejection 回調(diào)必須接受一個(gè) rejection 原因(可以是任何值)作為第一個(gè)參數(shù)
get 此時(shí)第三個(gè)參數(shù)為屬性名(字符串類型)
put 此時(shí)第三個(gè)參數(shù)為屬性名(字符串類型),第四個(gè)參數(shù)為新屬性值。
del 此時(shí)第三個(gè)參數(shù)為屬性名
post 此時(shí)第三個(gè)參數(shù)為方法的屬性名,接下來的變參為方法的調(diào)用參數(shù)
isDef
promiseSend方法的第二個(gè)參數(shù)為 resolver 方法
promiseSend方法可能接受變參
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+ 還提供了兼容性測試工具,以確定各個(gè)實(shí)現(xiàn)的兼容性。
實(shí)現(xiàn)一個(gè)迷你版本的 Promise上面扯了這么多規(guī)范,現(xiàn)在我們看看如何實(shí)現(xiàn)一個(gè)簡單而短小的 Promise 。
狀態(tài)機(jī)var PENDING = 0; var FULFILLED = 1; var REJECTED = 2; function Promise() { // store state which can be PENDING, FULFILLED or REJECTED var state = PENDING; // store value or error once FULFILLED or REJECTED var value = null; // store sucess & failure handlers attached by calling .then or .done var handlers = []; }狀態(tài)變遷
僅支持兩種狀態(tài)變遷, fulfill 和 reject
// ... function Promise() { // ... function fulfill(result) { state = FULFILLED; value = result; } function reject(error) { state = REJECTED; value = error; } }
fulfill 和 reject 方法較為底層,通常更高級(jí)的 resolve 方法開放給外部。
// ... function Promise() { // ... function resolve(result) { try { var then = getThen(result); if (then) { doResolve(then.bind(result), resolve, reject) return } fulfill(result); } catch (e) { reject(e); } } }
resolve 方法可以接受一個(gè)普通值或者另一個(gè) promise 作為參數(shù),如果接受一個(gè) promise 作為參數(shù),等待其完成。 promise 不允許被另一個(gè) promise fulfill ,所以需要開放 resolve 方法。 resolve 方法依賴一些幫助方法定義如下:
/** * Check if a value is a Promise and, if it is, * return the `then` method of that promise. * * @param {Promise|Any} value * @return {Function|Null} */ function getThen(value) { var t = typeof value; if (value && (t === "object" || t === "function")) { var then = value.then; if (typeof then === "function") { return then; } } return null; } /** * Take a potentially misbehaving resolver function and make sure * onFulfilled and onRejected are only called once. * * Makes no guarantees about asynchrony. * * @param {Function} fn A resolver function that may not be trusted * @param {Function} onFulfilled * @param {Function} onRejected */ function doResolve(fn, onFulfilled, onRejected) { var done = false; try { fn(function (value) { if (done) return done = true onFulfilled(value) }, function (reason) { if (done) return done = true onRejected(reason) }) } catch (ex) { if (done) return done = true onRejected(ex) } }
這里 resolve 和 doResolve 之間的遞歸很巧妙,用來處理 promise 的層層嵌套( promise 的 value 是一個(gè) promise )。
構(gòu)造器// ... function Promise(fn) { // ... doResolve(fn, resolve, reject); }.done 方法
// ... function Promise(fn) { // ... function handle(handler) { if (state === PENDING) { handlers.push(handler); } else { if (state === FULFILLED && typeof handler.onFulfilled === "function") { handler.onFulfilled(value); } if (state === REJECTED && typeof handler.onRejected === "function") { handler.onRejected(value); } } } this.done = function (onFulfilled, onRejected) { // ensure we are always asynchronous setTimeout(function () { handle({ onFulfilled: onFulfilled, onRejected: onRejected }); }, 0); } // ... }.then 方法
// ... function Promise(fn) { // ... this.then = function (onFulfilled, onRejected) { var self = this; return new Promise(function (resolve, reject) { return self.done(function (result) { if (typeof onFulfilled === "function") { try { return resolve(onFulfilled(result)); } catch (ex) { return reject(ex); } } else { return resolve(result); } }, function (error) { if (typeof onRejected === "function") { try { return resolve(onRejected(error)); } catch (ex) { return reject(ex); } } else { return reject(error); } }); }); } // ... }$.promise
jQuery 1.8 之前的版本, jQuery 的 then 方法只是一種可以同時(shí)調(diào)用 done 、 fail 和 progress 這三種回調(diào)的速寫方法,而 Promises/A 規(guī)范的 then 在行為上更像是 jQuery 的 pipe 。 jQuery 1.8 修正了這個(gè)問題,使 then 成為 pipe 的同義詞。不過,由于向后兼容的問題, jQuery 的 Promise 再如何對(duì) Promises/A 示好也不太會(huì)招人待見。
此外,在 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ì)于其工作原理卻有些懵懂和好奇。于是花了些經(jīng)理查閱并翻譯了 Promise 的規(guī)范,以充分的理解 Promise 的細(xì)節(jié)。
參考閱讀Promises/A
Promises/B
Promises/D
Promisejs
Promises/A+
As soon as possible
A minimalist implementation of a javascript promise
Lightweight implementation of promises
How is a promise/defer library implemented?
Basic Javascript promise implementation attempt
11. You"re Missing the Point of Promises
12. Boom! Promises/A+ Was Born
13. Futures and promises
14. JavaScript Promises - There and back again
Vangie Du將來的你,一定會(huì)感謝現(xiàn)在拼命努力的自己!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/78497.html
摘要:的翻譯文檔由的維護(hù)很多人說,阮老師已經(jīng)有一本關(guān)于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發(fā)過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會(huì)promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:函數(shù)會(huì)在之后的某個(gè)時(shí)刻觸發(fā)事件定時(shí)器。事件循環(huán)中的這樣一次遍歷被稱為一個(gè)。執(zhí)行完畢并出棧。當(dāng)定時(shí)器過期,宿主環(huán)境會(huì)把回調(diào)函數(shù)添加至事件循環(huán)隊(duì)列中,然后,在未來的某個(gè)取出并執(zhí)行該事件。 原文請(qǐng)查閱這里,略有改動(dòng)。 本系列持續(xù)更新中,Github 地址請(qǐng)查閱這里。 這是 JavaScript 工作原理的第四章。 現(xiàn)在,我們將會(huì)通過回顧單線程環(huán)境下編程的弊端及如何克服這些困難以創(chuàng)建令人驚嘆...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。寫一個(gè)符合規(guī)范并可配合使用的寫一個(gè)符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問題描述 在開發(fā)過程中,遇到一個(gè)需求:在系統(tǒng)初始化時(shí)通過http獲取一個(gè)第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個(gè)接口,可通過...
摘要:談起閉包,它可是兩個(gè)核心技術(shù)之一異步基于打造前端持續(xù)集成開發(fā)環(huán)境本文將以一個(gè)標(biāo)準(zhǔn)的項(xiàng)目為例,完全拋棄傳統(tǒng)的前端項(xiàng)目開發(fā)部署方式,基于容器技術(shù)打造一個(gè)精簡的前端持續(xù)集成的開發(fā)環(huán)境。 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果讀完本文還不懂,可以揍我。 不論你是javascript新手還是老鳥,不論是面試求職,還是日...
閱讀 1203·2021-11-23 10:10
閱讀 1548·2021-09-30 09:47
閱讀 931·2021-09-27 14:02
閱讀 3007·2019-08-30 15:45
閱讀 3045·2019-08-30 14:11
閱讀 3639·2019-08-29 14:05
閱讀 1845·2019-08-29 13:51
閱讀 2236·2019-08-29 11:33