徹底搞懂promise的各種方法和實現

前言

Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案回調函數和事件更合理更強大。它由社區(qū)最早提出和實現,ES6 將其寫進了語言標準,統(tǒng)一了用法,原生提供了Promise對象。本篇不注重講解promise的用法,關于用法,可以看阮一峰老師的ECMAScript 6系列里面的Promise部分:

ECMAScript 6 : Promise對象

本篇主要講解如何從零開始一步步的實現promise各項特性及功能,最終使其符合Promises/A+規(guī)范,因為講解較細,所以文章略長。另外,每一步的項目源碼都在github上,可以對照參考,每一步都有對應的項目代碼及測試代碼,喜歡的話,歡迎給個star~

開始

本文promise里用到的異步操作的示例都是使用的node里面的fs.readFile方法,在瀏覽器端可以使用setTimeout方法進行模擬異步操作。

一. 基礎版本

目標

可以創(chuàng)建promise對象實例。

promise實例傳入的異步方法執(zhí)行成功就執(zhí)行注冊的成功回調函數,失敗就執(zhí)行注冊的失敗回調函數。

實現

functionMyPromise(fn) {letself = this; // 緩存當前promise實例? ? self.value = null; //成功時的值? ? self.error = null; //失敗時的原因? ? self.onFulfilled = null; //成功的回調函數? ? self.onRejected = null; //失敗的回調函數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í)行到,所以回調函數還沒注冊上,這時reslove中調用成功回調肯定會報錯的。

目標

使promise支持同步方法

實現

functionresolve(value) {? ? //利用setTimeout特性將具體執(zhí)行放到then之后setTimeout(() => {? ? ? ? self.value = value;? ? ? ? self.onFulfilled(self.value)? ? })}functionreject(error) {setTimeout(() => {? ? ? ? self.error = error;? ? ? ? self.onRejected(self.error)? ? })}復制代碼

實現很簡單,就是在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)就凝固了,不會再變了,會一直保持這個結果,如果改變已經發(fā)生了,你再對promise對象添加回調函數,也會立即得到這個結果。

目標

實現promise的三種狀態(tài)。

實現promise對象的狀態(tài)改變,改變只有兩種可能:從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected。

實現一旦promise狀態(tài)改變,再對promise對象添加回調函數,也會立即得到這個結果。

實現

//定義三種狀態(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已經變?yōu)?fulfilled"或"rejected"就立刻執(zhí)行它的回調,并把結果傳入。

本節(jié)代碼

支持三種狀態(tài)代碼

四. 支持鏈式操作

我們平時寫promise一般都是對應的一組流程化的操作,如這樣:

promise.then(f1).then(f2).then(f3)

但是我們之前的版本最多只能注冊一個回調,這一節(jié)我們就來實現鏈式操作。

目標

使promise支持鏈式操作

實現

想支持鏈式操作,其實很簡單,首先存儲回調時要改為使用數組

self.onFulfilledCallbacks = [];self.onRejectedCallbacks = [];復制代碼

當然執(zhí)行回調時,也要改成遍歷回調數組執(zhí)行回調函數

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é)實現了鏈式調用,但是目前then方法里只能傳入同步任務,但是我們平常用promise,then方法里一般是異步任務,因為我們用promise主要用來解決一組流程化的異步操作,如下面這樣的調取接口獲取用戶id后,再根據用戶id調取接口獲取用戶余額,獲取用戶id和獲取用戶余額都需要調用接口,所以都是異步任務,如何使promise支持串行異步操作呢?

getUserId()? ? .then(getUserBalanceById)? ? .then(function(balance) {? ? ? ? //dosth? ? },function(error) {? ? ? ? console.log(error);? ? });復制代碼

目標

使promise支持串行異步操作

實現

這里為方便講解我們引入一個常見場景:用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é)實現的promise可以實現執(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復制代碼

所以要想實現異步操作串行,我們不能將回調函數都注冊在初始promise的onFulfilledCallbacks里面,而要將每個回調函數注冊在對應的異步操作promise的onFulfilledCallbacks里面,用讀取文件的場景來舉例,f1要在p的onFulfilledCallbacks里面,而f2應該在f1里面return的那個Promise的onFulfilledCallbacks里面,因為只有這樣才能實現讀取完2.txt后才去打印2.txt的結果。

但是,我們平常寫promise一般都是這樣寫的:promise.then(f1).then(f2).then(f3),一開始所有流程我們就指定好了,而不是在f1里面才去注冊f1的回調,f2里面才去注冊f2的回調。

如何既能保持這種鏈式寫法的同時又能使異步操作銜接執(zhí)行呢?我們其實讓then方法最后不再返回自身實例,而是返回一個新的promise即可,我們可以叫它bridgePromise,它最大的作用就是銜接后續(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);? ? ? ? ? ? ? ? }? ? ? ? ? ? });? ? ? ? })? ? }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);}//用來解析回調函數的返回值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);? ? }}復制代碼

首先,為防止使用者不傳成功回調函數或不失敗回調函數,我們給了默認回調函數,然后無論當前promise是什么狀態(tài),我們都返回一個bridgePromise用來銜接后續(xù)操作。

另外執(zhí)行回調函數時,因為回調函數既可能會返回一個異步的promise也可能會返回一個同步結果,所以我們把直接把回調函數的結果托管給bridgePromise,使用resolvePromise方法來解析回調函數的結果,如果回調函數返回一個promise并且狀態(tài)還是pending,就在這個promise的then方法中繼續(xù)解析這個promise reslove傳過來的值,如果值還是pending狀態(tài)的promise就繼續(xù)解析,直到不是一個異步promise,而是一個正常值就使用bridgePromise的reslove方法將bridgePromise的狀態(tài)改為fulfilled,并調用onFulfilledCallbacks回調數組中的方法,將該值傳入,到此異步操作就銜接上了。

這里很抽象,我們還是以文件順序讀取的場景畫一張圖解釋一下流程:

當執(zhí)行p.then(f1).then(f2).then(f3)時:

先執(zhí)行p.then(f1)返回了一個bridgePromise(p2),并在p的onFulfilledCallbacks回調列表中放入一個回調函數,回調函數負責執(zhí)行f1并且更新p2的狀態(tài).

然后.then(f2)時返回了一個bridgePromise(p3),這里注意其實是p2.then(f2),因為p.then(f1)時返回了p2。此時在p2的onFulfilledCallbacks回調列表中放入一個回調函數,回調函數負責執(zhí)行f2并且更新p3的狀態(tài).

然后.then(f3)時返回了一個bridgePromise(p4),并在p3的onFulfilledCallbacks回調列表中放入一個回調函數,回調函數負責執(zhí)行f3并且更新p4的狀態(tài).到此,回調關系注冊完了,如圖所示:

然后過了一段時間,p里面的異步操作執(zhí)行完了,讀取到了1.txt的內容,開始執(zhí)行p的回調函數,回調函數執(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的回調函數中去執(zhí)行。

p2的回調開始執(zhí)行,f2拿到傳過來的“this is 2.txt”參數開始執(zhí)行,打印出2.txt的內容,并將f2的返回值放到resolvePromise中開始解析,resolvePromise一看傳入了一個promise對象,promise是異步的啊,又得等著呢........后續(xù)操作就是不斷的重復4,5步直到結束。

到此,reslove這一條線已經我們已經走通,讓我們看看reject這一條線,reject其實處理起來很簡單:

首先執(zhí)行fn及執(zhí)行注冊的回調時都用try-catch包裹,無論哪里有異常都會進入reject分支。

一旦代碼進入reject分支直接將bridge promise設為rejected狀態(tài),于是后續(xù)都會走reject這個分支,另外如果不傳異常處理的onRejected函數,默認就是使用throw error將錯誤一直往后拋,達到了錯誤冒泡的目的。

最后可以實現一個catch函數用來接收錯誤。

MyPromise.prototype.catch =function(onRejected) {returnthis.then(null, onRejected);}復制代碼

到此,我們已經可以愉快的使用promise.then(f1).then(f2).then(f3).catch(errorLog)來順序讀取文件內容了。

本節(jié)代碼

支持串行異步任務代碼

六. 達到Promises/A+規(guī)范

其實,到支持串行異步任務這一節(jié),我們寫的promise在功能上已經基本齊全了,但是還不太規(guī)范,比如說一些其他情況的判斷等等,這一節(jié)我們就比著Promises/A+的規(guī)范打磨一下我們寫的promise。如果只是想學習promise的核心實現的,這一節(jié)看不懂也沒關系,因為這一節(jié)并沒有增加promise的功能,只是使promise更加規(guī)范,更加健壯。

目標

使promise達到Promises/A+規(guī)范,通過promises-aplus-tests的完整測試

實現

首先來可以了解一下Promises/A+規(guī)范:

Promises/A+規(guī)范原版

Promises/A+規(guī)范中文版

相比上一節(jié)代碼,本節(jié)代碼除了在resolvePromise函數里增加了幾個其他情況的判斷外,其他函數都沒有修改。完整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;? ? //這個判斷分支其實已經可以刪除,用下面那個分支代替,因為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 為對象或者函數? ? }elseif(x != null && ((typeof x ==='object') || (typeof x ==='function'))) {? ? ? ? try {? ? ? ? ? ? // 是否是thenable對象(具有then方法的對象/函數)? ? ? ? ? ? //2.3.3.1 將then賦為 x.thenletthen= x.then;if(typeofthen==='function') {? ? ? ? ? ? //2.3.3.3 如果then是一個函數,以x為this調用then函數,且第一個參數是resolvePromise,第二個參數是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不是一個函數,則 以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 為對象或者函數的判斷,這一條判斷主要對應2.3.3規(guī)范,中文規(guī)范如圖:

這一條標準對應的其實是thenable對象,什么是thenable對象,只要有then方法就是thenable對象,然后我們實現的時候照著規(guī)范實現就可以了。

elseif(x != null && ((typeof x ==='object') || (typeof x ==='function'))) {? ? ? ? try {? ? ? ? ? ? // 是否是thenable對象(具有then方法的對象/函數)? ? ? ? ? ? //2.3.3.1 將then賦為 x.thenletthen= x.then;if(typeofthen==='function') {? ? ? ? ? ? //2.3.3.3 如果then是一個函數,以x為this調用then函數,且第一個參數是resolvePromise,第二個參數是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不是一個函數,則以x為值fulfill promise。? ? ? ? ? ? ? ? resolve(x);? ? ? ? ? ? }? ? ? ? } catch (e) {? ? ? ? //2.3.3.2 如果在取x.then值時拋出了異常,則以這個異常做為原因將promise拒絕。if(called)return;? ? ? ? ? ? called =true;? ? ? ? ? ? reject(e);? ? ? ? }? ? }復制代碼

再寫完這個分支的代碼后,其實我們已經可以刪除if (x instanceof MyPromise) {}這個分支的代碼,因為promise也是一個thenable對象,完全可以使用上述代碼兼容代替。另外,本節(jié)代碼很多重復代碼可以封裝優(yōu)化一下,但是為了看得清晰,并沒有進行抽象封裝,大家如果覺得重復代碼太多的話,可以自行抽象封裝。

本節(jié)代碼

達到Promises/A+規(guī)范代碼

七. 實現 promise 的all,race,resolve,reject方法

上一節(jié)我們已經實現了一個符合Promises/A+規(guī)范的promise,本節(jié)我們把一些es6 promise里的常用方法實現一下。

目標

實現es6 promise的all,race,resolve,reject方法

實現

我們還是在之前的基礎上繼續(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的主線邏輯實現后,這些方法都不難實現,all的原理就是返回一個promise,在這個promise中給所有傳入的promise的then方法中都注冊上回調,回調成功了就把值放到結果數組中,所有回調都成功了就讓返回的這個promise去reslove,把結果數組返回出去,race和all大同小異,只不過它不會等所有promise都成功,而是誰快就把誰返回出去,resolve和reject的邏輯也很簡單,看一下就明白了。

本節(jié)代碼

實現all,race,resolve,reject方法代碼

八. 實現 promiseify 方法

其實到上一節(jié)為止,promise的方法已經都講完了,這一節(jié)講一個著名promise庫bluebird里面的方法promiseify,因為這個方法很常用而且以前面試還被問過。promiseify有什么作用呢?它的作用就是將異步回調函數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);})復制代碼

目標

實現bluebird的promiseify方法

實現

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])? ? ? ? ? ? }));? ? ? ? })? ? }}復制代碼

雖然方法很強大,但是實現起來并沒有很難,想在外邊直接調用promise的方法那就返回一個promise唄,內部將原來參數后面拼接一個回調函數參數,在回調函數里執(zhí)行這個promise的reslove方法把結果傳出去,promiseify就實現了。

本節(jié)代碼

實現promiseify方法

最后

不知不覺寫了這么多了,大家如果覺得還可以就給個贊唄,另外每一節(jié)的代碼都托管到了github上,大家可以對照看那一節(jié)的promise實現代碼及測試代碼,也順便求個star~

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

友情鏈接更多精彩內容