摘要:使用是極好的,它是如此有用以至于我覺得應(yīng)該好好研究一下,甚至是實(shí)現(xiàn)一個(gè)簡易的版本。構(gòu)造函數(shù)檢查參數(shù)例如是不是函數(shù)啊初始化,創(chuàng)建對(duì)象執(zhí)行因此構(gòu)造函數(shù)里面?zhèn)魅氲氖橇⒓幢粓?zhí)行的。
使用Promise是極好的,它是如此有用以至于我覺得應(yīng)該好好研究一下Promise,甚至是實(shí)現(xiàn)一個(gè)簡易的版本。實(shí)現(xiàn)之前,我們先來看看Promise的用途:
使用Promise callback hellPromise的第一個(gè)用途是能夠很好地解決回調(diào)黑洞的問題,假設(shè)要實(shí)現(xiàn)一個(gè)用戶展示的任務(wù),這個(gè)任務(wù)分為三步:
獲取用戶信息
獲取用戶圖像
彈窗提示
不使用Promise,我們的實(shí)現(xiàn)可能是這樣子:
getUserInfo(id, function (info) { getUserImage(info.img, function () { showTip(); }) })
這里只是三步,如果有更長串的任務(wù)時(shí),我們就會(huì)陷入到回調(diào)黑洞之中,為了解決這個(gè)問題,我們就可以使用Promise來處理這一長串任務(wù),使用Promise的版本是這樣子的:
// getUserInfo返回promise getUserInfo(id) .then(getUserImage) .then(showTip) .catch(function (e) { console.log(e); });
原來向右發(fā)展的代碼,開始向下發(fā)展,這樣也更適合編程習(xí)慣,如果要讓我們的代碼更加健壯,我們就需要在每一步來處理錯(cuò)誤信息,使用promise這后,我們只需要在最后的catch中做善后處理。
并發(fā)假如我們要顯示某一個(gè)頁的10條記錄,但是我們只有一個(gè)通過id獲取記錄的接口,這樣我們就需要發(fā)送10個(gè)請(qǐng)求,并且所有請(qǐng)求都完成之后再將記錄全部添加到頁面之中,Promise在這個(gè)場(chǎng)景下使用是特別合適的。
代碼可能是這樣子:
// ids要獲取信息的所有記錄id // getRecordById獲取記錄的接口,返回promise Promise.all(ids.map(getRecordById)) .then(showRecords) .catch(function (e) { console.log(e); });
這就是Promise的一些簡單的用途,當(dāng)然令人興奮的是Promise已經(jīng)是ES6的標(biāo)準(zhǔn),而且目前很多瀏覽器已經(jīng)原生支持Promise了。對(duì)于那些無法使用Promise的瀏覽器,我們就只能自己去實(shí)現(xiàn)了,下面就來看看Promise的簡單實(shí)現(xiàn)吧。
實(shí)現(xiàn) warm up先來盜用一張MDN的圖,先來熱熱身,看看Promise的狀態(tài)遷移:
Promise有三種狀態(tài):
pending:初始狀態(tài), 非 fulfilled 或 rejected
fulfilled: 成功的操作
rejected: 失敗的操作
我們可以看出新建的Promise是pending狀態(tài),fulfill之后就會(huì)執(zhí)行調(diào)用then的回調(diào)函數(shù)了,倘若reject了就會(huì)調(diào)用catch來進(jìn)行異常處理了,并且無論是調(diào)用then還是catch都會(huì)返回新的promise,這就是為什么promise可以鏈?zhǔn)秸{(diào)用了。
接著,我們來研究一下規(guī)范是怎么描述
promise的。這里只抽取核心部分,邊界問題不考慮。
檢查參數(shù):例如executor是不是函數(shù)啊
初始化:[[State]]=pending,[[FulfillReactions]]=[],[[RejectReactions]]=[]
創(chuàng)建resolve對(duì)象:{[[Resolve]]: resolve, [[Reject]]: reject}
執(zhí)行executor:executor(resolve, reject)
因此構(gòu)造函數(shù)里面?zhèn)魅氲膃xcuter是立即被執(zhí)行的。FulfillReactions存儲(chǔ)著promise執(zhí)行成功時(shí)要做的操作,RejectReactions存儲(chǔ)著promise是要執(zhí)行的操作。
function Promise(resolver) { this._id = counter++; this._state = PENDING; this._result = undefined; this._subscribers = []; var promise = this; if (noop !== resolver) { try { resolver(function (value) { resolve(promise, value); }, function (reason) { reject(promise, reason); }); } catch (e) { reject(promise, e); } } }FulfillPromise(promise, value)
檢查[[state]],必須為pending(不是pending的表示已經(jīng)解析,不能重復(fù)解析)
賦值:[[Result]]=value,[[state]]=fulfilled
觸發(fā)[[FulfillReactions]]的操作
和FulfillPromise聯(lián)系最緊密的就是ResolvePromise了,這里我們給出的是ResolvePromise的實(shí)現(xiàn),區(qū)別只是多了直接解析Promise。
function resolve(promise, value) { // 要resolve的為promise(then的callback返回的是promise) if (typeof value === "object" && promise.constructor === value.constructor) { handleOwnThenable(promise, value); } // 要resolve的是值 else { if (promise._state !== PENDING) { return; } promise._result = value; promise._state = FULFILLED; asap(publish, promise); } } function handleOwnThenable(promise, thenable) { // 如果返回的promise已經(jīng)完成 // 直接用該promise的值resolve父promise if (thenable._state === FULFILLED) { resolve(promise, thenable._result); } else if (thenable._state === REJECTED) { reject(promise, thenable._result); } // 如果返回的promise未完成 // 要等該promise完成再resolve父promise else { subscribe(thenable, undefined, function(value) { resolve(promise, value); }, function(reason) { reject(promise, reason); }); } }RejectPromise(promise, reason)
檢查[[state]],必須為pending(不是pending的表示已經(jīng)解析,不能重復(fù)解析)
賦值:[[Result]]=reason,[[state]]=rejected
觸發(fā)[[RejectReactions]]的操作
觸發(fā)[[FulfillReactions]]和觸發(fā)[[RejectReactions]]實(shí)際就是遍歷數(shù)組,執(zhí)行所有的回調(diào)函數(shù)。
function reject(promise, reason) { if (promise._state !== PENDING) { return; } promise._state = REJECTED; promise._result = reason; asap(publish, promise); }Promise.prototype.then(onFullfilled, onRejected)
promise=this
新建resultCapability三元組,{[[Promise]], [[Resolve]], [[Reject]]}([[Promise]]新建的)
fulfillReaction={[[Capabilities]]: resultCapability, [[Handler]]: onFulfilled}
rejectReaction={[[Capabilities]]: resultCapability, [[Handler]]: onRejected}
如果[[state]]是pending:fulfillReaction加入[[FulfillReactions]],rejectReaction加入[[RejectReactions]]
如果[[state]]是fulfilled:fulfillReaction加入執(zhí)行隊(duì)列
如果[[state]]是rejected:rejectReaction加入執(zhí)行隊(duì)列
返回resultCapability.[[Promise]]
這里可以看出構(gòu)造函數(shù)和then的關(guān)系是很緊密的,新建的promise如果是異步操作,那么狀態(tài)就是pending,調(diào)用then時(shí)會(huì)新建子promise,并且將回調(diào)操作加入父promise的[[FulfillReactions]]或[[RejectReactions]]的數(shù)組里,這實(shí)際就是發(fā)布訂閱模式。
他們是這樣的關(guān)系:
無論是new promise還是調(diào)用then或catch,都會(huì)得到一個(gè)新的promise,這些promise都會(huì)訂閱父級(jí)promise的完成事件,父級(jí)promise完成之后就會(huì)執(zhí)行一系列的回調(diào)操作,也就是發(fā)布。
Promise.prototype.catch(onRejected)then的語法糖:then(null, onRejected)
下面就是Promise原型:
Promise.prototype = { constructor: Promise, then: function (onFulfillment, onRejection) { var parent = this; var state = parent._state; if (state === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) { return this; } var child = new Promise(noop); var result = parent._result; if (state) { var callback = arguments[state - 1]; asap(function () { invokeCallback(state, child, callback, result); }); } else { subscribe(parent, child, onFulfillment, onRejection); } return child; }, "catch": function (onRejection) { return this.then(null, onRejection); } };Promise.resolve(value)
新建promise
調(diào)用ResolvePromise(promise, value)(未列出,會(huì)判斷一些情況然后調(diào)用FulfillPromise)
返回promise
Promise.resolve = function (arg) { var child = new Promise(noop); resolve(child, arg); return child; };Promise.reject(value)
新建promise
調(diào)用RejectPromise(promise, value)
返回promise
Promise.reject = function (reason) { var child = new Promise(noop); reject(child, reason); return child; };Promise.all(iterator)
到這里我們已經(jīng)能夠?qū)崿F(xiàn)基本的promise了,Promise.all和Promise.race就不繼續(xù)描述了,有興趣的可以繼續(xù)去讀規(guī)范,這里上圖來說明我對(duì)這兩個(gè)函數(shù)的理解:
調(diào)用promise.all會(huì)新建一個(gè)對(duì)象來存儲(chǔ)所有promise的處理狀態(tài),保存執(zhí)行的結(jié)果,當(dāng)remain為0時(shí),就可以resolve 新建的promise,這樣就可以繼續(xù)往后執(zhí)行了。
Promise.all = function (promises) { var child = new Promise(noop); var record = { remain: promises.length, values: [] }; promises.forEach(function (promise, i) { if (promise._state === PENDING) { subscribe(promise, undefined, onFulfilled(i), onRejected); } else if (promise._state === REJECTED) { reject(child, promise._result); return false; } else { --record.remain; record.values[i] = promise._result; if (record.remain == 0) { resolve(child, values); } } }); return child; function onFulfilled(i) { return function (val) { --record.remain; record.values[i] = val; if (record.remian === 0) { resolve(child, record.values); } } } function onRejected(reason) { reject(child, reason); } };Promise.race(iterator)
promise.race與promise.all類似,不過只要有一個(gè)promise完成了,我們就可以resolve新建的promise了。
Promise.race = function (promises) { var child = new Promise(noop); promises.forEach(function (promise, i) { if (promise._state === PENDING) { subscribe(promise, undefined, onFulfilled, onRejected); } else if (promise._state === REJECTED) { reject(child, promise._result); return false; } else { resolve(child, promise._result); return false; } }); return child; function onFulfilled(val) { resolve(child, val); } function onRejected(reason) { reject(child, reason); } };
這就是promise的基本內(nèi)容了,完整代碼請(qǐng)戳這里。
其他問題 promises 穿透如果傳入then里面的參數(shù)不是函數(shù),就會(huì)被忽略,這就是promise穿透的原因,所以永遠(yuǎn)往then里面?zhèn)鬟f函數(shù)。答案可以從then方法里面調(diào)用的一個(gè)關(guān)鍵函數(shù)invokeCallback中找到答案:
function invokeCallback(settled, promise, callback, detail) { var hasCallback = (typeof callback === "function"), value, error, succeeded, failed; if (hasCallback) { try { value = callback(detail); } catch (e) { value = { error: e }; } if (value && !!value.error) { failed = true; error = value.error; value = null; } else { succeeded = true; } } // then的參數(shù)不是函數(shù) // 會(huì)被忽略,也就是promise穿透 else { value = detail; succeeded = true; } if (promise._state === PENDING) { if (hasCallback && succeeded || settled === FULFILLED) { resolve(promise, value); } else if (failed || settled === REJECTED) { reject(promise, error); } } }
例如如下例子,結(jié)果都是輸出foo:
Promise.resolve("foo").then(Promise.resolve("bar")).then(function (result) { console.log(result); }); Promise.resolve("foo").then(null).then(function (result) { console.log(result); });擁抱金字塔
promise能夠很好的解決金字塔問題,但是有時(shí)候我們也是需要適當(dāng)使用金字塔的,例如我們要同時(shí)獲取兩個(gè)promise的結(jié)果,但是這兩個(gè)promise是有關(guān)聯(lián)的,也就是有順序的,該怎么辦?
也許解決方案會(huì)是這樣,定義一個(gè)全局變量,這樣在第二個(gè)then里面就可以使用兩個(gè)promise的結(jié)果了。
var user; getUserByName("nolan").then(function (result) { user = result; return getUserAccountById(user.id); }).then(function (userAccount) { // 好了, "user" 和 "userAccount" 都有了 });
但是這不是最好的方案,此時(shí)何不拋棄成見,擁抱金字塔:
getUserByName("nolan").then(function (user) { return getUserAccountById(user.id).then(function (userAccount) { // 好了, "user" 和 "userAccount" 都有了 }); });
promise是如此強(qiáng)大而且難以理解,但是抓住實(shí)質(zhì)之后其實(shí)并沒有想象的那么復(fù)雜,這也是為什么我要寫下這篇文章。更過關(guān)于如何正確使用promise,請(qǐng)看第三篇參考文章,強(qiáng)力推薦。
參考實(shí)現(xiàn)promise
MDN-Promise
ES6-Promise規(guī)范
We have a problem with promises
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/85939.html
摘要:構(gòu)造函數(shù)的實(shí)現(xiàn)我們?cè)谑褂玫臅r(shí)候其實(shí)是使用關(guān)鍵字創(chuàng)建了一個(gè)的實(shí)例,其實(shí)是一個(gè)類,即構(gòu)造函數(shù),下面來實(shí)現(xiàn)構(gòu)造函數(shù)。 showImg(https://segmentfault.com/img/remote/1460000018998456); 閱讀原文 概述 Promise 是 js 異步編程的一種解決方案,避免了 回調(diào)地獄 給編程帶來的麻煩,在 ES6 中成為了標(biāo)準(zhǔn),這篇文章重點(diǎn)不是敘...
摘要:本次的任務(wù)假如。。。。。引擎發(fā)生了重大故障,方法變成了,為了拯救世界,需要開發(fā)一個(gè)模塊來解決此問題。實(shí)現(xiàn)首先要知道是什么是對(duì)異步編程的一種抽象。數(shù)組中任何一個(gè)為的話,則整個(gè)調(diào)用會(huì)立即終止,并返回一個(gè)的新的對(duì)象。 本次的任務(wù) 假如。。。。。 JavaScript v8 引擎發(fā)生了重大故障,Promise.all 方法變成了 undefined ,為了拯救 JavaScript 世界,需要...
摘要:通過或者拿到方法回調(diào)函數(shù)的返回值,然后調(diào)用,將新增的的和傳入到中。打印結(jié)果實(shí)現(xiàn)方法接收一個(gè)包含多個(gè)的數(shù)組,當(dāng)有一個(gè)為狀態(tài)時(shí),整個(gè)大的為,并執(zhí)行回調(diào)函數(shù)。 前言 Promise大家一定都不陌生了,JavaScript異步流程從最初的Callback,到Promise,到Generator,再到目前使用最多的Async/Await(如果對(duì)于這些不熟悉的可以參考我另一篇文章《JavaScri...
摘要:內(nèi)部總體上分為兩種情況,一種是當(dāng)前對(duì)象狀態(tài)已經(jīng)變?yōu)榛颍藭r(shí)則直接把響應(yīng)的回調(diào)函數(shù)添加到異步隊(duì)列中,另一種情況是當(dāng)前對(duì)象狀態(tài)還是,此時(shí)則把響應(yīng)的回調(diào)函數(shù)依次添加到數(shù)組中。 今天,我?guī)е蠹乙徊揭徊礁?guī)范實(shí)現(xiàn)一個(gè)自己的Promise,大家可以對(duì)照我的第二篇文章Promise介紹--規(guī)范篇或官方規(guī)范來一一學(xué)習(xí)。 Promise內(nèi)部有三個(gè)固定的狀態(tài),我們?cè)谖募刑崆岸x。 const PEN...
摘要:如果實(shí)現(xiàn)滿足所有要求,則實(shí)現(xiàn)可能允許。本條款允許使用特定于實(shí)現(xiàn)的方法來采用已知一致承諾的狀態(tài)。接下來根據(jù)規(guī)范進(jìn)行手寫實(shí)現(xiàn)注釋偷懶就將對(duì)應(yīng)的規(guī)范標(biāo)注出來,其實(shí)基本上就是對(duì)著規(guī)范實(shí)現(xiàn)。 如果要手寫實(shí)現(xiàn)promise,那么先看看promise/A+規(guī)范,再來實(shí)現(xiàn),將會(huì)事半功倍。那么我先翻譯一下Promise/A+規(guī)范中的內(nèi)容。 術(shù)語 1.1 promise 是一個(gè)帶有符合此規(guī)范的the...
閱讀 771·2021-11-11 16:54
閱讀 3097·2021-09-26 09:55
閱讀 2041·2021-09-07 10:20
閱讀 1230·2019-08-30 10:58
閱讀 1073·2019-08-28 18:04
閱讀 724·2019-08-26 13:57
閱讀 3624·2019-08-26 13:45
閱讀 1184·2019-08-26 11:42