徹底搞懂promise的各種方法和實現(xiàn)

前言

Promise 是異步編程的一種解決方案婆芦,比傳統(tǒng)的解決方案回調函數(shù)和事件更合理更強大。它由社區(qū)最早提出和實現(xiàn),ES6 將其寫進了語言標準姨拥,統(tǒng)一了用法,原生提供了Promise對象笋婿。本篇不注重講解promise的用法而芥,關于用法可都,可以看阮一峰老師的ECMAScript 6系列里面的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é)代碼

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

四. 支持鏈式操作

我們平時寫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ī)范:

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

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é)代碼

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

七. 實現(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é)代碼

實現(xiàn)promiseify方法

最后

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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末博肋,一起剝皮案震驚了整個濱河市低斋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌匪凡,老刑警劉巖膊畴,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異病游,居然都是意外死亡唇跨,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門衬衬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來买猖,“玉大人,你說我怎么就攤上這事佣耐≌” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵兼砖,是天一觀的道長奸远。 經(jīng)常有香客問我既棺,道長,這世上最難降的妖魔是什么懒叛? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任丸冕,我火速辦了婚禮,結果婚禮上薛窥,老公的妹妹穿的比我還像新娘胖烛。我一直安慰自己,他們只是感情好诅迷,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布佩番。 她就那樣靜靜地躺著,像睡著了一般罢杉。 火紅的嫁衣襯著肌膚如雪趟畏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天滩租,我揣著相機與錄音赋秀,去河邊找鬼。 笑死律想,一個胖子當著我的面吹牛猎莲,可吹牛的內容都是我干的。 我是一名探鬼主播技即,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼著洼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了姥份?” 一聲冷哼從身側響起郭脂,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤年碘,失蹤者是張志新(化名)和其女友劉穎澈歉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體屿衅,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡埃难,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了涤久。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涡尘。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖响迂,靈堂內的尸體忽然破棺而出考抄,到底是詐尸還是另有隱情,我是刑警寧澤蔗彤,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布川梅,位于F島的核電站疯兼,受9級特大地震影響,放射性物質發(fā)生泄漏贫途。R本人自食惡果不足惜吧彪,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望丢早。 院中可真熱鬧姨裸,春花似錦、人聲如沸怨酝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽农猬。三九已至扑毡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盛险,已是汗流浹背瞄摊。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留苦掘,地道東北人换帜。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像鹤啡,于是被迫代替她去往敵國和親惯驼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348