前言
Promise 是異步編程的一種解決方案婆芦,比傳統(tǒng)的解決方案回調函數(shù)和事件更合理更強大。它由社區(qū)最早提出和實現(xiàn),ES6 將其寫進了語言標準姨拥,統(tǒng)一了用法,原生提供了Promise對象笋婿。本篇不注重講解promise的用法而芥,關于用法可都,可以看阮一峰老師的ECMAScript 6系列里面的Promise部分:
本篇主要講解如何從零開始一步步的實現(xiàn)promise各項特性及功能啄枕,最終使其符合Promises/A+規(guī)范婚陪,因為講解較細族沃,所以文章略長频祝。另外泌参,每一步的項目源碼都在github上,可以對照參考常空,每一步都有對應的項目代碼及測試代碼沽一,喜歡的話,歡迎給個star~
開始
本文promise里用到的異步操作的示例都是使用的node里面的fs.readFile方法漓糙,在瀏覽器端可以使用setTimeout方法進行模擬異步操作铣缠。
一. 基礎版本
目標
可以創(chuàng)建promise對象實例。
promise實例傳入的異步方法執(zhí)行成功就執(zhí)行注冊的成功回調函數(shù)昆禽,失敗就執(zhí)行注冊的失敗回調函數(shù)蝗蛙。
實現(xiàn)
functionMyPromise(fn) {letself = this; // 緩存當前promise實例? ? self.value = null; //成功時的值? ? self.error = null; //失敗時的原因? ? self.onFulfilled = null; //成功的回調函數(shù)? ? self.onRejected = null; //失敗的回調函數(shù)functionresolve(value) {? ? ? ? self.value = value;? ? ? ? self.onFulfilled(self.value);//resolve時執(zhí)行成功回調? ? }functionreject(error) {? ? ? ? self.error = error;? ? ? ? self.onRejected(self.error)//reject時執(zhí)行失敗回調? ? }? ? fn(resolve, reject);}MyPromise.prototype.then =function(onFulfilled, onRejected) {? ? //在這里給promise實例注冊成功和失敗回調? ? this.onFulfilled = onFulfilled;? ? this.onRejected = onRejected;}module.exports = MyPromise復制代碼
代碼很短,邏輯也非常清晰醉鳖,在then中注冊了這個promise實例的成功回調和失敗回調捡硅,當promise reslove時,就把異步執(zhí)行結果賦值給promise實例的value盗棵,并把這個值傳入成功回調中執(zhí)行壮韭,失敗就把異步執(zhí)行失敗原因賦值給promise實例的error,并把這個值傳入失敗回調并執(zhí)行纹因。
本節(jié)代碼
二. 支持同步任務
我們知道喷屋,我們在使用es6 的promise時,可以傳入一個異步任務瞭恰,也可以傳入一個同步任務屯曹,但是我們的上面基礎版代碼并不支持同步任務,如果我們這樣寫就會報錯:
letpromise = new Promise((resolve, reject) => {? ? resolve("同步任務執(zhí)行")});復制代碼
為什么呢惊畏?因為是同步任務是牢,所以當我們的promise實例reslove時,它的then方法還沒執(zhí)行到陕截,所以回調函數(shù)還沒注冊上驳棱,這時reslove中調用成功回調肯定會報錯的。
目標
使promise支持同步方法
實現(xiàn)
functionresolve(value) {? ? //利用setTimeout特性將具體執(zhí)行放到then之后setTimeout(() => {? ? ? ? self.value = value;? ? ? ? self.onFulfilled(self.value)? ? })}functionreject(error) {setTimeout(() => {? ? ? ? self.error = error;? ? ? ? self.onRejected(self.error)? ? })}復制代碼
實現(xiàn)很簡單农曲,就是在reslove和reject里面用setTimeout進行包裹社搅,使其到then方法執(zhí)行之后再去執(zhí)行,這樣我們就讓promise支持傳入同步方法乳规,另外形葬,關于這一點,Promise/A+規(guī)范里也明確要求了這一點暮的。
2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code.
本節(jié)代碼
三. 支持三種狀態(tài)
我們知道在使用promise時笙以,promise有三種狀態(tài):pending(進行中)、fulfilled(已成功)和rejected(已失敹潮纭)猖腕。只有異步操作的結果拆祈,可以決定當前是哪一種狀態(tài),任何其他操作都無法改變這個狀態(tài)倘感。另外放坏,promise一旦狀態(tài)改變,就不會再變老玛,任何時候都可以得到這個結果promise對象的狀態(tài)改變淤年,只有兩種可能:從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected。只要這兩種情況發(fā)生蜡豹,狀態(tài)就凝固了麸粮,不會再變了,會一直保持這個結果镜廉,如果改變已經(jīng)發(fā)生了豹休,你再對promise對象添加回調函數(shù),也會立即得到這個結果桨吊。
目標
實現(xiàn)promise的三種狀態(tài)威根。
實現(xiàn)promise對象的狀態(tài)改變,改變只有兩種可能:從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected视乐。
實現(xiàn)一旦promise狀態(tài)改變洛搀,再對promise對象添加回調函數(shù),也會立即得到這個結果佑淀。
實現(xiàn)
//定義三種狀態(tài)const PENDING ="pending";const FULFILLED ="fulfilled";const REJECTED ="rejected";functionMyPromise(fn) {letself = this;? ? self.value = null;? ? self.error = null;? ? self.status = PENDING;? ? self.onFulfilled = null;? ? self.onRejected = null;functionresolve(value) {? ? ? ? //如果狀態(tài)是pending才去修改狀態(tài)為fulfilled并執(zhí)行成功邏輯if(self.status === PENDING) {setTimeout(function() {? ? ? ? ? ? ? ? self.status = FULFILLED;? ? ? ? ? ? ? ? self.value = value;? ? ? ? ? ? ? ? self.onFulfilled(self.value);? ? ? ? ? ? })? ? ? ? }? ? }functionreject(error) {? ? ? ? //如果狀態(tài)是pending才去修改狀態(tài)為rejected并執(zhí)行失敗邏輯if(self.status === PENDING) {setTimeout(function() {? ? ? ? ? ? ? ? self.status = REJECTED;? ? ? ? ? ? ? ? self.error = error;? ? ? ? ? ? ? ? self.onRejected(self.error);? ? ? ? ? ? })? ? ? ? }? ? }? ? fn(resolve, reject);}MyPromise.prototype.then =function(onFulfilled, onRejected) {if(this.status === PENDING) {? ? ? ? this.onFulfilled = onFulfilled;? ? ? ? this.onRejected = onRejected;? ? }elseif(this.status === FULFILLED) {? ? ? ? //如果狀態(tài)是fulfilled留美,直接執(zhí)行成功回調,并將成功值傳入? ? ? ? onFulfilled(this.value)? ? }else{? ? ? ? //如果狀態(tài)是rejected伸刃,直接執(zhí)行失敗回調谎砾,并將失敗原因傳入? ? ? ? onRejected(this.error)? ? }returnthis;}module.exports = MyPromise復制代碼
首先,我們建立了三種狀態(tài)"pending","fulfilled","rejected",然后我們在reslove和reject中做判斷捧颅,只有狀態(tài)是pending時景图,才去改變promise的狀態(tài),并執(zhí)行相應操作碉哑,另外挚币,我們在then中判斷,如果這個promise已經(jīng)變?yōu)?fulfilled"或"rejected"就立刻執(zhí)行它的回調扣典,并把結果傳入妆毕。
本節(jié)代碼
四. 支持鏈式操作
我們平時寫promise一般都是對應的一組流程化的操作,如這樣:
promise.then(f1).then(f2).then(f3)
但是我們之前的版本最多只能注冊一個回調贮尖,這一節(jié)我們就來實現(xiàn)鏈式操作笛粘。
目標
使promise支持鏈式操作
實現(xiàn)
想支持鏈式操作,其實很簡單,首先存儲回調時要改為使用數(shù)組
self.onFulfilledCallbacks = [];self.onRejectedCallbacks = [];復制代碼
當然執(zhí)行回調時薪前,也要改成遍歷回調數(shù)組執(zhí)行回調函數(shù)
self.onFulfilledCallbacks.forEach((callback) => callback(self.value));復制代碼
最后润努,then方法也要改一下,只需要在最后一行加一個return this即可,這其實和jQuery鏈式操作的原理一致序六,每次調用完方法都返回自身實例,后面的方法也是實例的方法蚤吹,所以可以繼續(xù)執(zhí)行例诀。
MyPromise.prototype.then =function(onFulfilled, onRejected) {if(this.status === PENDING) {? ? ? ? this.onFulfilledCallbacks.push(onFulfilled);? ? ? ? this.onRejectedCallbacks.push(onRejected);? ? }elseif(this.status === FULFILLED) {? ? ? ? onFulfilled(this.value)? ? }else{? ? ? ? onRejected(this.error)? ? }returnthis;}復制代碼
本節(jié)代碼
五. 支持串行異步任務
我們上一節(jié)實現(xiàn)了鏈式調用,但是目前then方法里只能傳入同步任務裁着,但是我們平常用promise繁涂,then方法里一般是異步任務,因為我們用promise主要用來解決一組流程化的異步操作二驰,如下面這樣的調取接口獲取用戶id后扔罪,再根據(jù)用戶id調取接口獲取用戶余額,獲取用戶id和獲取用戶余額都需要調用接口桶雀,所以都是異步任務矿酵,如何使promise支持串行異步操作呢?
getUserId()? ? .then(getUserBalanceById)? ? .then(function(balance) {? ? ? ? //dosth? ? },function(error) {? ? ? ? console.log(error);? ? });復制代碼
目標
使promise支持串行異步操作
實現(xiàn)
這里為方便講解我們引入一個常見場景:用promise順序讀取文件內容,場景代碼如下:
letp = new Promise((resolve, reject) => {? ? fs.readFile('../file/1.txt',"utf8",function(err, data) {? ? ? ? err ? reject(err) : resolve(data)? ? });});letf1 =function(data) {? ? console.log(data)returnnew Promise((resolve, reject) => {? ? ? ? fs.readFile('../file/2.txt',"utf8",function(err, data) {? ? ? ? ? ? err ? reject(err) : resolve(data)? ? ? ? });? ? });}letf2 =function(data) {? ? console.log(data)returnnew Promise((resolve, reject) => {? ? ? ? fs.readFile('../file/3.txt',"utf8",function(err, data) {? ? ? ? ? ? err ? reject(err) : resolve(data)? ? ? ? });? ? });}letf3 =function(data) {? ? console.log(data);}leterrorLog =function(error) {? ? console.log(error)}p.then(f1).then(f2).then(f3).catch(errorLog)//會依次輸出//this is 1.txt//this is 2.txt//this is 3.txt復制代碼
上面場景矗积,我們讀取完1.txt后并打印1.txt內容全肮,再去讀取2.txt并打印2.txt內容,再去讀取3.txt并打印3.txt內容棘捣,而讀取文件都是異步操作辜腺,所以都是返回一個promise,我們上一節(jié)實現(xiàn)的promise可以實現(xiàn)執(zhí)行完異步操作后執(zhí)行后續(xù)回調乍恐,但是本節(jié)的回調讀取文件內容操作并不是同步的评疗,而是異步的,所以當讀取完1.txt后茵烈,執(zhí)行它回調onFulfilledCallbacks里面的f1百匆,f2,f3時呜投,異步操作還沒有完成胧华,所以我們本想得到這樣的輸出:
this is 1.txtthis is 2.txtthis is 3.txt復制代碼
但是實際上卻會輸出
this is 1.txtthis is 1.txtthis is 1.txt復制代碼
所以要想實現(xiàn)異步操作串行,我們不能將回調函數(shù)都注冊在初始promise的onFulfilledCallbacks里面宙彪,而要將每個回調函數(shù)注冊在對應的異步操作promise的onFulfilledCallbacks里面矩动,用讀取文件的場景來舉例,f1要在p的onFulfilledCallbacks里面释漆,而f2應該在f1里面return的那個Promise的onFulfilledCallbacks里面悲没,因為只有這樣才能實現(xiàn)讀取完2.txt后才去打印2.txt的結果。
但是,我們平常寫promise一般都是這樣寫的:promise.then(f1).then(f2).then(f3)示姿,一開始所有流程我們就指定好了甜橱,而不是在f1里面才去注冊f1的回調,f2里面才去注冊f2的回調栈戳。
如何既能保持這種鏈式寫法的同時又能使異步操作銜接執(zhí)行呢岂傲?我們其實讓then方法最后不再返回自身實例,而是返回一個新的promise即可子檀,我們可以叫它bridgePromise镊掖,它最大的作用就是銜接后續(xù)操作,我們看下具體實現(xiàn)代碼:
MyPromise.prototype.then =function(onFulfilled, onRejected) {? ? const self = this;letbridgePromise;? ? //防止使用者不傳成功或失敗回調函數(shù)褂痰,所以成功失敗回調都給了默認回調函數(shù)? ? onFulfilled = typeof onFulfilled ==="function"? onFulfilled : value => value;? ? onRejected = typeof onRejected ==="function"? onRejected : error => { throw error };if(self.status === FULFILLED) {returnbridgePromise = new MyPromise((resolve, reject) => {setTimeout(() => {? ? ? ? ? ? ? ? try {letx = onFulfilled(self.value);? ? ? ? ? ? ? ? ? ? resolvePromise(bridgePromise, x, resolve, reject);? ? ? ? ? ? ? ? } catch (e) {? ? ? ? ? ? ? ? ? ? reject(e);? ? ? ? ? ? ? ? }? ? ? ? ? ? });? ? ? ? })? ? }if(self.status === REJECTED) {returnbridgePromise = new MyPromise((resolve, reject) => {setTimeout(() => {? ? ? ? ? ? ? ? try {letx = onRejected(self.error);? ? ? ? ? ? ? ? ? ? resolvePromise(bridgePromise, x, resolve, reject);? ? ? ? ? ? ? ? } catch (e) {? ? ? ? ? ? ? ? ? ? reject(e);? ? ? ? ? ? ? ? }? ? ? ? ? ? });? ? ? ? });? ? }if(self.status === PENDING) {returnbridgePromise = new MyPromise((resolve, reject) => {? ? ? ? ? ? self.onFulfilledCallbacks.push((value) => {? ? ? ? ? ? ? ? try {letx = onFulfilled(value);? ? ? ? ? ? ? ? ? ? resolvePromise(bridgePromise, x, resolve, reject);? ? ? ? ? ? ? ? } catch (e) {? ? ? ? ? ? ? ? ? ? reject(e);? ? ? ? ? ? ? ? }? ? ? ? ? ? });? ? ? ? ? ? self.onRejectedCallbacks.push((error) => {? ? ? ? ? ? ? ? try {letx = onRejected(error);? ? ? ? ? ? ? ? ? ? resolvePromise(bridgePromise, x, resolve, reject);? ? ? ? ? ? ? ? } catch (e) {? ? ? ? ? ? ? ? ? ? reject(e);? ? ? ? ? ? ? ? }? ? ? ? ? ? });? ? ? ? });? ? }}//catch方法其實是個語法糖亩进,就是只傳onRejected不傳onFulfilled的then方法MyPromise.prototype.catch =function(onRejected) {returnthis.then(null, onRejected);}//用來解析回調函數(shù)的返回值x,x可能是普通值也可能是個promise對象functionresolvePromise(bridgePromise, x, resolve, reject) {? //如果x是一個promiseif(x instanceof MyPromise) {? ? ? ? //如果這個promise是pending狀態(tài)缩歪,就在它的then方法里繼續(xù)執(zhí)行resolvePromise解析它的結果归薛,直到返回值不是一個pending狀態(tài)的promise為止if(x.status === PENDING) {? ? ? ? ? ? x.then(y => {? ? ? ? ? ? ? ? resolvePromise(bridgePromise, y, resolve, reject);? ? ? ? ? ? }, error => {? ? ? ? ? ? ? ? reject(error);? ? ? ? ? ? });? ? ? ? }else{? ? ? ? ? ? x.then(resolve, reject);? ? ? ? }? ? ? ? //如果x是一個普通值,就讓bridgePromise的狀態(tài)fulfilled匪蝙,并把這個值傳遞下去? ? }else{? ? ? ? resolve(x);? ? }}復制代碼
首先主籍,為防止使用者不傳成功回調函數(shù)或不失敗回調函數(shù),我們給了默認回調函數(shù)逛球,然后無論當前promise是什么狀態(tài)崇猫,我們都返回一個bridgePromise用來銜接后續(xù)操作。
另外執(zhí)行回調函數(shù)時,因為回調函數(shù)既可能會返回一個異步的promise也可能會返回一個同步結果需忿,所以我們把直接把回調函數(shù)的結果托管給bridgePromise诅炉,使用resolvePromise方法來解析回調函數(shù)的結果,如果回調函數(shù)返回一個promise并且狀態(tài)還是pending屋厘,就在這個promise的then方法中繼續(xù)解析這個promise reslove傳過來的值涕烧,如果值還是pending狀態(tài)的promise就繼續(xù)解析,直到不是一個異步promise汗洒,而是一個正常值就使用bridgePromise的reslove方法將bridgePromise的狀態(tài)改為fulfilled议纯,并調用onFulfilledCallbacks回調數(shù)組中的方法,將該值傳入溢谤,到此異步操作就銜接上了瞻凤。
這里很抽象,我們還是以文件順序讀取的場景畫一張圖解釋一下流程:
當執(zhí)行p.then(f1).then(f2).then(f3)時:
先執(zhí)行p.then(f1)返回了一個bridgePromise(p2)世杀,并在p的onFulfilledCallbacks回調列表中放入一個回調函數(shù)阀参,回調函數(shù)負責執(zhí)行f1并且更新p2的狀態(tài).
然后.then(f2)時返回了一個bridgePromise(p3),這里注意其實是p2.then(f2)瞻坝,因為p.then(f1)時返回了p2蛛壳。此時在p2的onFulfilledCallbacks回調列表中放入一個回調函數(shù),回調函數(shù)負責執(zhí)行f2并且更新p3的狀態(tài).
然后.then(f3)時返回了一個bridgePromise(p4),并在p3的onFulfilledCallbacks回調列表中放入一個回調函數(shù)衙荐,回調函數(shù)負責執(zhí)行f3并且更新p4的狀態(tài).到此捞挥,回調關系注冊完了,如圖所示:
然后過了一段時間忧吟,p里面的異步操作執(zhí)行完了砌函,讀取到了1.txt的內容,開始執(zhí)行p的回調函數(shù)溜族,回調函數(shù)執(zhí)行f1讹俊,打印出1.txt的內容“this is 1.txt”,并將f1的返回值放到resolvePromise中開始解析斩祭。resolvePromise一看傳入了一個promise對象劣像,promise是異步的啊乡话,得等著呢摧玫,于是就在這個promise對象的then方法中繼續(xù)resolvePromise這個promise對象resolve的結果,一看不是promise對象了绑青,而是一個具體值“this is 2.txt”诬像,于是調用bridgePromise(p2)的reslove方法將bridgePromise(p2)的狀態(tài)更新為fulfilled,并將“this is 2.txt”傳入p2的回調函數(shù)中去執(zhí)行闸婴。
p2的回調開始執(zhí)行坏挠,f2拿到傳過來的“this is 2.txt”參數(shù)開始執(zhí)行,打印出2.txt的內容邪乍,并將f2的返回值放到resolvePromise中開始解析降狠,resolvePromise一看傳入了一個promise對象,promise是異步的啊庇楞,又得等著呢........后續(xù)操作就是不斷的重復4,5步直到結束榜配。
到此,reslove這一條線已經(jīng)我們已經(jīng)走通吕晌,讓我們看看reject這一條線蛋褥,reject其實處理起來很簡單:
首先執(zhí)行fn及執(zhí)行注冊的回調時都用try-catch包裹,無論哪里有異常都會進入reject分支睛驳。
一旦代碼進入reject分支直接將bridge promise設為rejected狀態(tài)烙心,于是后續(xù)都會走reject這個分支,另外如果不傳異常處理的onRejected函數(shù)乏沸,默認就是使用throw error將錯誤一直往后拋淫茵,達到了錯誤冒泡的目的。
最后可以實現(xiàn)一個catch函數(shù)用來接收錯誤蹬跃。
MyPromise.prototype.catch =function(onRejected) {returnthis.then(null, onRejected);}復制代碼
到此痘昌,我們已經(jīng)可以愉快的使用promise.then(f1).then(f2).then(f3).catch(errorLog)來順序讀取文件內容了。
本節(jié)代碼
六. 達到Promises/A+規(guī)范
其實,到支持串行異步任務這一節(jié)辆苔,我們寫的promise在功能上已經(jīng)基本齊全了算灸,但是還不太規(guī)范,比如說一些其他情況的判斷等等驻啤,這一節(jié)我們就比著Promises/A+的規(guī)范打磨一下我們寫的promise菲驴。如果只是想學習promise的核心實現(xiàn)的,這一節(jié)看不懂也沒關系骑冗,因為這一節(jié)并沒有增加promise的功能赊瞬,只是使promise更加規(guī)范,更加健壯贼涩。
目標
使promise達到Promises/A+規(guī)范巧涧,通過promises-aplus-tests的完整測試
實現(xiàn)
首先來可以了解一下Promises/A+規(guī)范:
相比上一節(jié)代碼,本節(jié)代碼除了在resolvePromise函數(shù)里增加了幾個其他情況的判斷外遥倦,其他函數(shù)都沒有修改谤绳。完整promise代碼如下:
const PENDING ="pending";const FULFILLED ="fulfilled";const REJECTED ="rejected";functionMyPromise(fn) {? ? const self = this;? ? self.value = null;? ? self.error = null;? ? self.status = PENDING;? ? self.onFulfilledCallbacks = [];? ? self.onRejectedCallbacks = [];functionresolve(value) {if(value instanceof MyPromise) {returnvalue.then(resolve, reject);? ? ? ? }if(self.status === PENDING) {setTimeout(() => {? ? ? ? ? ? ? ? self.status = FULFILLED;? ? ? ? ? ? ? ? self.value = value;? ? ? ? ? ? ? ? self.onFulfilledCallbacks.forEach((callback) => callback(self.value));? ? ? ? ? ? }, 0)? ? ? ? }? ? }functionreject(error) {if(self.status === PENDING) {setTimeout(function() {? ? ? ? ? ? ? ? self.status = REJECTED;? ? ? ? ? ? ? ? self.error = error;? ? ? ? ? ? ? ? self.onRejectedCallbacks.forEach((callback) => callback(self.error));? ? ? ? ? ? }, 0)? ? ? ? }? ? }? ? try {? ? ? ? fn(resolve, reject);? ? } catch (e) {? ? ? ? reject(e);? ? }}functionresolvePromise(bridgepromise, x, resolve, reject) {? ? //2.3.1規(guī)范,避免循環(huán)引用if(bridgepromise === x) {returnreject(new TypeError('Circular reference'));? ? }letcalled =false;? ? //這個判斷分支其實已經(jīng)可以刪除袒哥,用下面那個分支代替缩筛,因為promise也是一個thenable對象if(x instanceof MyPromise) {if(x.status === PENDING) {? ? ? ? ? ? x.then(y => {? ? ? ? ? ? ? ? resolvePromise(bridgepromise, y, resolve, reject);? ? ? ? ? ? }, error => {? ? ? ? ? ? ? ? reject(error);? ? ? ? ? ? });? ? ? ? }else{? ? ? ? ? ? x.then(resolve, reject);? ? ? ? }? ? ? ? // 2.3.3規(guī)范,如果 x 為對象或者函數(shù)? ? }elseif(x != null && ((typeof x ==='object') || (typeof x ==='function'))) {? ? ? ? try {? ? ? ? ? ? // 是否是thenable對象(具有then方法的對象/函數(shù))? ? ? ? ? ? //2.3.3.1 將then賦為 x.thenletthen= x.then;if(typeofthen==='function') {? ? ? ? ? ? //2.3.3.3 如果then是一個函數(shù)堡称,以x為this調用then函數(shù)瞎抛,且第一個參數(shù)是resolvePromise,第二個參數(shù)是rejectPromise? ? ? ? ? ? ? ? then.call(x, y => {if(called)return;? ? ? ? ? ? ? ? ? ? called =true;? ? ? ? ? ? ? ? ? ? resolvePromise(bridgepromise, y, resolve, reject);? ? ? ? ? ? ? ? }, error => {if(called)return;? ? ? ? ? ? ? ? ? ? called =true;? ? ? ? ? ? ? ? ? ? reject(error);? ? ? ? ? ? ? ? })? ? ? ? ? ? }else{? ? ? ? ? ? //2.3.3.4 如果then不是一個函數(shù)却紧,則 以x為值fulfill promise桐臊。? ? ? ? ? ? ? ? resolve(x);? ? ? ? ? ? }? ? ? ? } catch (e) {? ? ? ? //2.3.3.2 如果在取x.then值時拋出了異常,則以這個異常做為原因將promise拒絕晓殊。if(called)return;? ? ? ? ? ? called =true;? ? ? ? ? ? reject(e);? ? ? ? }? ? }else{? ? ? ? resolve(x);? ? }}MyPromise.prototype.then =function(onFulfilled, onRejected) {? ? const self = this;letbridgePromise;? ? onFulfilled = typeof onFulfilled ==="function"? onFulfilled : value => value;? ? onRejected = typeof onRejected ==="function"? onRejected : error => { throw error };if(self.status === FULFILLED) {returnbridgePromise = new MyPromise((resolve, reject) => {setTimeout(() => {? ? ? ? ? ? ? ? try {letx = onFulfilled(self.value);? ? ? ? ? ? ? ? ? ? resolvePromise(bridgePromise, x, resolve, reject);? ? ? ? ? ? ? ? } catch (e) {? ? ? ? ? ? ? ? ? ? reject(e);? ? ? ? ? ? ? ? }? ? ? ? ? ? }, 0);? ? ? ? })? ? }if(self.status === REJECTED) {returnbridgePromise = new MyPromise((resolve, reject) => {setTimeout(() => {? ? ? ? ? ? ? ? try {letx = onRejected(self.error);? ? ? ? ? ? ? ? ? ? resolvePromise(bridgePromise, x, resolve, reject);? ? ? ? ? ? ? ? } catch (e) {? ? ? ? ? ? ? ? ? ? reject(e);? ? ? ? ? ? ? ? }? ? ? ? ? ? }, 0);? ? ? ? });? ? }if(self.status === PENDING) {returnbridgePromise = new MyPromise((resolve, reject) => {? ? ? ? ? ? self.onFulfilledCallbacks.push((value) => {? ? ? ? ? ? ? ? try {letx = onFulfilled(value);? ? ? ? ? ? ? ? ? ? resolvePromise(bridgePromise, x, resolve, reject);? ? ? ? ? ? ? ? } catch (e) {? ? ? ? ? ? ? ? ? ? reject(e);? ? ? ? ? ? ? ? }? ? ? ? ? ? });? ? ? ? ? ? self.onRejectedCallbacks.push((error) => {? ? ? ? ? ? ? ? try {letx = onRejected(error);? ? ? ? ? ? ? ? ? ? resolvePromise(bridgePromise, x, resolve, reject);? ? ? ? ? ? ? ? } catch (e) {? ? ? ? ? ? ? ? ? ? reject(e);? ? ? ? ? ? ? ? }? ? ? ? ? ? });? ? ? ? });? ? }}MyPromise.prototype.catch =function(onRejected) {returnthis.then(null, onRejected);}// 執(zhí)行測試用例需要用到的代碼MyPromise.deferred =function() {letdefer = {};? ? defer.promise = new MyPromise((resolve, reject) => {? ? ? ? defer.resolve = resolve;? ? ? ? defer.reject = reject;? ? });returndefer;}try {? ? module.exports = MyPromise} catch (e) {}復制代碼
我們可以先跑一下測試断凶,需要安裝一下測試插件,然后執(zhí)行測試,測試時注意在加上上面最后的那幾行代碼才能執(zhí)行測試用例挺物。
1.npm i -g promises-aplus-tests2.promises-aplus-tests mypromise.js復制代碼
運行測試用例可以看到懒浮,我們上面寫的promise代碼通過了完整的Promises/A+規(guī)范測試。
先撒花高興一下~??ヽ(°▽°)ノ?
然后開始分析我們這一節(jié)的代碼识藤,我們主要在resolvePromise里加了額外的兩個判斷砚著,第一個是x和bridgePromise是指向相同值時,報出循環(huán)引用的錯誤痴昧,使promise符合2.3.1規(guī)范稽穆,然后我們增加了一個x 為對象或者函數(shù)的判斷,這一條判斷主要對應2.3.3規(guī)范赶撰,中文規(guī)范如圖:
這一條標準對應的其實是thenable對象舌镶,什么是thenable對象柱彻,只要有then方法就是thenable對象,然后我們實現(xiàn)的時候照著規(guī)范實現(xiàn)就可以了。
elseif(x != null && ((typeof x ==='object') || (typeof x ==='function'))) {? ? ? ? try {? ? ? ? ? ? // 是否是thenable對象(具有then方法的對象/函數(shù))? ? ? ? ? ? //2.3.3.1 將then賦為 x.thenletthen= x.then;if(typeofthen==='function') {? ? ? ? ? ? //2.3.3.3 如果then是一個函數(shù)餐胀,以x為this調用then函數(shù)哟楷,且第一個參數(shù)是resolvePromise,第二個參數(shù)是rejectPromise? ? ? ? ? ? ? ? then.call(x, y => {if(called)return;? ? ? ? ? ? ? ? ? ? called =true;? ? ? ? ? ? ? ? ? ? resolvePromise(bridgepromise, y, resolve, reject);? ? ? ? ? ? ? ? }, error => {if(called)return;? ? ? ? ? ? ? ? ? ? called =true;? ? ? ? ? ? ? ? ? ? reject(error);? ? ? ? ? ? ? ? })? ? ? ? ? ? }else{? ? ? ? ? ? //2.3.3.4 如果then不是一個函數(shù)否灾,則以x為值fulfill promise卖擅。? ? ? ? ? ? ? ? resolve(x);? ? ? ? ? ? }? ? ? ? } catch (e) {? ? ? ? //2.3.3.2 如果在取x.then值時拋出了異常,則以這個異常做為原因將promise拒絕墨技。if(called)return;? ? ? ? ? ? called =true;? ? ? ? ? ? reject(e);? ? ? ? }? ? }復制代碼
再寫完這個分支的代碼后惩阶,其實我們已經(jīng)可以刪除if (x instanceof MyPromise) {}這個分支的代碼,因為promise也是一個thenable對象扣汪,完全可以使用上述代碼兼容代替断楷。另外,本節(jié)代碼很多重復代碼可以封裝優(yōu)化一下崭别,但是為了看得清晰冬筒,并沒有進行抽象封裝,大家如果覺得重復代碼太多的話紊遵,可以自行抽象封裝账千。
本節(jié)代碼
七. 實現(xiàn) promise 的all侥蒙,race暗膜,resolve,reject方法
上一節(jié)我們已經(jīng)實現(xiàn)了一個符合Promises/A+規(guī)范的promise鞭衩,本節(jié)我們把一些es6 promise里的常用方法實現(xiàn)一下学搜。
目標
實現(xiàn)es6 promise的all,race论衍,resolve瑞佩,reject方法
實現(xiàn)
我們還是在之前的基礎上繼續(xù)往下寫:
MyPromise.all =function(promises) {returnnew MyPromise(function(resolve, reject) {letresult = [];letcount = 0;for(leti = 0; i < promises.length; i++) {? ? ? ? ? ? promises[i].then(function(data) {? ? ? ? ? ? ? ? result[i] = data;if(++count == promises.length) {? ? ? ? ? ? ? ? ? ? resolve(result);? ? ? ? ? ? ? ? }? ? ? ? ? ? },function(error) {? ? ? ? ? ? ? ? reject(error);? ? ? ? ? ? });? ? ? ? }? ? });}MyPromise.race =function(promises) {returnnew MyPromise(function(resolve, reject) {for(leti = 0; i < promises.length; i++) {? ? ? ? ? ? promises[i].then(function(data) {? ? ? ? ? ? ? ? resolve(data);? ? ? ? ? ? },function(error) {? ? ? ? ? ? ? ? reject(error);? ? ? ? ? ? });? ? ? ? }? ? });}MyPromise.resolve =function(value) {returnnew MyPromise(resolve => {? ? ? ? resolve(value);? ? });}MyPromise.reject =function(error) {returnnew MyPromise((resolve, reject) => {? ? ? ? reject(error);? ? });}復制代碼
其實前幾節(jié)把promise的主線邏輯實現(xiàn)后,這些方法都不難實現(xiàn)坯台,all的原理就是返回一個promise炬丸,在這個promise中給所有傳入的promise的then方法中都注冊上回調,回調成功了就把值放到結果數(shù)組中蜒蕾,所有回調都成功了就讓返回的這個promise去reslove稠炬,把結果數(shù)組返回出去,race和all大同小異咪啡,只不過它不會等所有promise都成功首启,而是誰快就把誰返回出去,resolve和reject的邏輯也很簡單撤摸,看一下就明白了毅桃。
本節(jié)代碼
實現(xiàn)all褒纲,race,resolve钥飞,reject方法代碼
八. 實現(xiàn) promiseify 方法
其實到上一節(jié)為止莺掠,promise的方法已經(jīng)都講完了,這一節(jié)講一個著名promise庫bluebird里面的方法promiseify读宙,因為這個方法很常用而且以前面試還被問過汁蝶。promiseify有什么作用呢?它的作用就是將異步回調函數(shù)api轉換為promise形式论悴,比如下面這個掖棉,對fs.readFile 執(zhí)行promiseify后,就可以直接用promise的方式去調用讀取文件的方法了膀估,是不是很強大幔亥。
letPromise = require('./bluebird');letfs = require("fs");varreadFile = Promise.promisify(fs.readFile);readFile("1.txt","utf8").then(function(data) {? ? console.log(data);})復制代碼
目標
實現(xiàn)bluebird的promiseify方法
實現(xiàn)
MyPromise.promisify =function(fn) {returnfunction() {? ? ? ? var args = Array.from(arguments);returnnew MyPromise(function(resolve, reject) {? ? ? ? ? ? fn.apply(null, args.concat(function(err) {? ? ? ? ? ? ? ? err ? reject(err) : resolve(arguments[1])? ? ? ? ? ? }));? ? ? ? })? ? }}復制代碼
雖然方法很強大,但是實現(xiàn)起來并沒有很難察纯,想在外邊直接調用promise的方法那就返回一個promise唄帕棉,內部將原來參數(shù)后面拼接一個回調函數(shù)參數(shù),在回調函數(shù)里執(zhí)行這個promise的reslove方法把結果傳出去饼记,promiseify就實現(xiàn)了香伴。
本節(jié)代碼
最后
不知不覺寫了這么多了,大家如果覺得還可以就給個贊唄具则,另外每一節(jié)的代碼都托管到了github上即纲,大家可以對照看那一節(jié)的promise實現(xiàn)代碼及測試代碼,也順便求個star~