Promise几乎是面试必考点,因此咱们不能仅仅会用,还得知道他的底层原理,学习他原理的最好方法就是本身也实现一个Promise。因此本文会本身实现一个遵循Promise/A+
规范的Promise。实现以后,咱们还要用Promise/A+
官方的测试工具来测试下咱们的实现是否正确,这个工具总共有872个测试用例,所有经过才算是符合Promise/A+
规范,下面是他们的连接:javascript
Promise/A+
规范: https://github.com/promises-aplus/promises-specjava
Promise/A+
测试工具: https://github.com/promises-aplus/promises-testsgit
本文的完整代码托管在GitHub上: https://github.com/dennis-jiang/Front-End-Knowledges/blob/master/Examples/JavaScript/Promise/MyPromise.jsgithub
Promise的基本用法,网上有不少,我这里简单提一下,我仍是用三个相互依赖的网络请求作例子,假如咱们有三个网络请求,请求2必须依赖请求1的结果,请求3必须依赖请求2的结果,若是用回调的话会有三层,会陷入“回调地狱”,用Promise就清晰多了:面试
const request = require("request"); // 咱们先用Promise包装下三个网络请求 // 请求成功时resolve这个Promise const request1 = function() { const promise = new Promise((resolve) => { request('https://www.baidu.com', function (error, response) { if (!error && response.statusCode == 200) { resolve('request1 success'); } }); }); return promise; } const request2 = function() { const promise = new Promise((resolve) => { request('https://www.baidu.com', function (error, response) { if (!error && response.statusCode == 200) { resolve('request2 success'); } }); }); return promise; } const request3 = function() { const promise = new Promise((resolve) => { request('https://www.baidu.com', function (error, response) { if (!error && response.statusCode == 200) { resolve('request3 success'); } }); }); return promise; } // 先发起request1,等他resolve后再发起request2, // 而后是request3 request1().then((data) => { console.log(data); return request2(); }) .then((data) => { console.log(data); return request3(); }) .then((data) => { console.log(data); })
上面的例子里面,then
是能够链式调用的,后面的then
能够拿到前面resolve
出来的数据,咱们控制台能够看到三个success依次打出来:npm
经过上面的例子,其实咱们已经知道了一个promise长什么样子,Promises/A+规范其实就是对这个长相进一步进行了规范。下面我会对这个规范进行一些讲解。json
promise
:是一个拥有then
方法的对象或函数,其行为符合本规范thenable
:是一个定义了then
方法的对象或函数。这个主要是用来兼容一些老的Promise实现,只要一个Promise实现是thenable,也就是拥有then
方法的,就能够跟Promises/A+兼容。value
:指reslove
出来的值,能够是任何合法的JS值(包括undefined
, thenable 和 promise等)exception
:异常,在Promise里面用throw
抛出来的值reason
:拒绝缘由,是reject
里面传的参数,表示reject
的缘由
Promise总共有三个状态:数组
pending
: 一个promise在resolve或者reject前就处于这个状态。fulfilled
: 一个promise被resolve后就处于fulfilled
状态,这个状态不能再改变,并且必须拥有一个不可变的值(value
)。rejected
: 一个promise被reject后就处于rejected
状态,这个状态也不能再改变,并且必须拥有一个不可变的拒绝缘由(reason
)。
注意这里的不可变指的是===
,也就是说,若是value
或者reason
是对象,只要保证引用不变就行,规范没有强制要求里面的属性也不变。Promise状态其实很简单,画张图就是:promise
一个promise必须拥有一个then
方法来访问他的值或者拒绝缘由。then
方法有两个参数:网络
promise.then(onFulfilled, onRejected)
onFulfilled
和 onRejected
都是可选参数。
onFulfilled
不是函数,其必须被忽略onRejected
不是函数,其必须被忽略onFulfilled
特性若是 onFulfilled
是函数:
promise
执行结束后其必须被调用,其第一个参数为 promise
的终值value
promise
执行结束前其不可被调用onRejected
特性若是 onRejected
是函数:
promise
被拒绝执行后其必须被调用,其第一个参数为 promise
的据因reason
promise
被拒绝执行前其不可被调用then
方法能够被同一个 promise
调用屡次
promise
成功执行时,全部 onFulfilled
需按照其注册顺序依次回调promise
被拒绝执行时,全部的 onRejected
需按照其注册顺序依次回调then
方法必须返回一个 promise
对象。
promise2 = promise1.then(onFulfilled, onRejected);
onFulfilled
或者 onRejected
返回一个值 x
,则运行 Promise 解决过程:[[Resolve]](promise2, x)
onFulfilled
或者 onRejected
抛出一个异常 e
,则 promise2
必须拒绝执行,并返回拒因 e
onFulfilled
不是函数且 promise1
成功执行, promise2
必须成功执行并返回相同的值onRejected
不是函数且 promise1
拒绝执行, promise2
必须拒绝执行并返回相同的据因规范里面还有很大一部分是讲解Promise 解决过程的,光看规范,很空洞,前面这些规范已经能够指导咱们开始写一个本身的Promise了,Promise 解决过程会在咱们后面写到了再详细讲解。
咱们本身要写一个Promise,确定须要知道有哪些工做须要作,咱们先从Promise的使用来窥探下须要作啥:
- 新建Promise须要使用
new
关键字,那他确定是做为面向对象的方式调用的,Promise是一个类。关于JS的面向对象更详细的解释能够看这篇文章。- 咱们
new Promise(fn)
的时候须要传一个函数进去,说明Promise的参数是一个函数- 构造函数传进去的
fn
会收到resolve
和reject
两个函数,用来表示Promise成功和失败,说明构造函数里面还须要resolve
和reject
这两个函数,这两个函数的做用是改变Promise的状态。- 根据规范,promise有
pending
,fulfilled
,rejected
三个状态,初始状态为pending
,调用resolve
会将其改成fulfilled
,调用reject
会改成rejected
。- promise实例对象建好后能够调用
then
方法,并且是能够链式调用then
方法,说明then
是一个实例方法。链式调用的实现这篇有详细解释,我这里再也不赘述。简单的说就是then
方法也必须返回一个带then
方法的对象,能够是this或者新的promise实例。
为了更好的兼容性,本文就不用ES6了。
// 先定义三个常量表示状态 var PENDING = 'pending'; var FULFILLED = 'fulfilled'; var REJECTED = 'rejected'; function MyPromise(fn) { this.status = PENDING; // 初始状态为pending this.value = null; // 初始化value this.reason = null; // 初始化reason }
resolve
和reject
方法根据规范,resolve
方法是将状态改成fulfilled,reject
是将状态改成rejected。
// 这两个方法直接写在构造函数里面 function MyPromise(fn) { // ...省略前面代码... // 存一下this,以便resolve和reject里面访问 var that = this; // resolve方法参数是value function resolve(value) { if(that.status === PENDING) { that.status = FULFILLED; that.value = value; } } // reject方法参数是reason function reject(reason) { if(that.status === PENDING) { that.status = REJECTED; that.reason = reason; } } }
最后将resolve
和reject
做为参数调用传进来的参数,记得加上try
,若是捕获到错误就reject
。
function MyPromise(fn) { // ...省略前面代码... try { fn(resolve, reject); } catch (error) { reject(error); } }
then
方法根据咱们前面的分析,then
方法能够链式调用,因此他是实例方法,并且规范中的API是promise.then(onFulfilled, onRejected)
,咱们先把架子搭出来:
MyPromise.prototype.then = function(onFulfilled, onRejected) {}
那then
方法里面应该干什么呢,其实规范也告诉咱们了,先检查onFulfilled
和onRejected
是否是函数,若是不是函数就忽略他们,所谓“忽略”并非什么都不干,对于onFulfilled
来讲“忽略”就是将value
原封不动的返回,对于onRejected
来讲就是返回reason
,onRejected
由于是错误分支,咱们返回reason
应该throw一个Error:
MyPromise.prototype.then = function(onFulfilled, onRejected) { // 若是onFulfilled不是函数,给一个默认函数,返回value var realOnFulfilled = onFulfilled; if(typeof realOnFulfilled !== 'function') { realOnFulfilled = function (value) { return value; } } // 若是onRejected不是函数,给一个默认函数,返回reason的Error var realOnRejected = onRejected; if(typeof realOnRejected !== 'function') { realOnRejected = function (reason) { throw reason; } } }
参数检查完后就该干点真正的事情了,想一想咱们使用Promise的时候,若是promise操做成功了就会调用then
里面的onFulfilled
,若是他失败了,就会调用onRejected
。对应咱们的代码就应该检查下promise的status,若是是FULFILLED
,就调用onFulfilled
,若是是REJECTED
,就调用onRejected
:
MyPromise.prototype.then = function(onFulfilled, onRejected) { // ...省略前面代码... if(this.status === FULFILLED) { onFulfilled(this.value) } if(this.status === REJECTED) { onRejected(this.reason); } }
再想一下,咱们新建一个promise的时候多是直接这样用的:
new Promise(fn).then(onFulfilled, onRejected);
上面代码then
是在实例对象一建立好就调用了,这时候fn
里面的异步操做可能还没结束呢,也就是说他的status
仍是PENDING
,这怎么办呢,这时候咱们确定不能当即调onFulfilled
或者onRejected
的,由于fn
到底成功仍是失败还不知道呢。那何时知道fn
成功仍是失败呢?答案是fn
里面主动调resolve
或者reject
的时候。因此若是这时候status
状态仍是PENDING
,咱们应该将onFulfilled
和onRejected
两个回调存起来,等到fn
有告终论,resolve
或者reject
的时候再来调用对应的代码。由于后面then
还有链式调用,会有多个onFulfilled
和onRejected
,我这里用两个数组将他们存起来,等resolve
或者reject
的时候将数组里面的所有方法拿出来执行一遍:
// 构造函数 function MyPromise(fn) { // ...省略其余代码... // 构造函数里面添加两个数组存储成功和失败的回调 this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; function resolve(value) { if(that.status === PENDING) { // ...省略其余代码... // resolve里面将全部成功的回调拿出来执行 that.onFulfilledCallbacks.forEach(callback => { callback(that.value); }); } } function reject(reason) { if(that.status === PENDING) { // ...省略其余代码... // resolve里面将全部失败的回调拿出来执行 that.onRejectedCallbacks.forEach(callback => { callback(that.reason); }); } } } // then方法 MyPromise.prototype.then = function(onFulfilled, onRejected) { // ...省略其余代码... // 若是仍是PENDING状态,将回调保存下来 if(this.status === PENDING) { this.onFulfilledCallbacks.push(realOnFulfilled); this.onRejectedCallbacks.push(realOnRejected); } }
上面这种暂时将回调保存下来,等条件知足的时候再拿出来运行让我想起了一种模式:订阅发布模式。咱们往回调数组里面push
回调函数,其实就至关于往事件中心注册事件了,resolve
就至关于发布了一个成功事件,全部注册了的事件,即onFulfilledCallbacks
里面的全部方法都会拿出来执行,同理reject
就至关于发布了一个失败事件。更多订阅发布模式的原理能够看这里。
到这里为止,其实咱们已经能够实现异步调用了,只是then
的返回值还没实现,还不能实现链式调用,咱们先来玩一下:
var request = require("request"); var MyPromise = require('./MyPromise'); var promise1 = new MyPromise((resolve) => { request('https://www.baidu.com', function (error, response) { if (!error && response.statusCode == 200) { resolve('request1 success'); } }); }); promise1.then(function(value) { console.log(value); }); var promise2 = new MyPromise((resolve, reject) => { request('https://www.baidu.com', function (error, response) { if (!error && response.statusCode == 200) { reject('request2 failed'); } }); }); promise2.then(function(value) { console.log(value); }, function(reason) { console.log(reason); });
上述代码输出以下图,符合咱们的预期,说明到目前为止,咱们的代码都没问题:
then
的返回值根据规范then
的返回值必须是一个promise,规范还定义了不一样状况应该怎么处理,咱们先来处理几种比较简单的状况:
onFulfilled
或者 onRejected
抛出一个异常 e
,则 promise2
必须拒绝执行,并返回拒因 e
。MyPromise.prototype.then = function(onFulfilled, onRejected) { // ... 省略其余代码 ... // 有了这个要求,在RESOLVED和REJECTED的时候就不能简单的运行onFulfilled和onRejected了。 // 咱们须要将他们用try...catch...包起来,若是有错就reject。 if(this.status === FULFILLED) { var promise2 = new MyPromise(function(resolve, reject) { try { realOnFulfilled(that.value); } catch (error) { reject(error); } }); return promise2; } if(this.status === REJECTED) { var promise2 = new MyPromise(function(resolve, reject) { try { realOnRejected(that.reason); } catch (error) { reject(error); } }); return promise2; } // 若是仍是PENDING状态,也不能直接保存回调方法了,须要包一层来捕获错误 if(this.status === PENDING) { var promise2 = new MyPromise(function(resolve, reject) { that.onFulfilledCallbacks.push(function() { try { realOnFulfilled(that.value); } catch (error) { reject(error); } }); that.onRejectedCallbacks.push(function() { try { realOnRejected(that.reason); } catch (error) { reject(error); } }); }); return promise2; } }
onFulfilled
不是函数且 promise1
成功执行, promise2
必须成功执行并返回相同的值// 咱们就根据要求加个判断,注意else里面是正常执行流程,须要resolve // 这是个例子,每一个realOnFulfilled后面都要这样写 if(this.status === FULFILLED) { var promise2 = new MyPromise(function(resolve, reject) { try { if (typeof onFulfilled !== 'function') { resolve(that.value); } else { realOnFulfilled(that.value); resolve(that.value); } } catch (error) { reject(error); } }); return promise2; }
onRejected
不是函数且 promise1
拒绝执行, promise2
必须拒绝执行并返回相同的据因。这个要求其实在咱们检测 onRejected
不是函数的时候已经作到了,由于咱们默认给的onRejected
里面会throw一个Error,因此代码确定会走到catch里面去。可是咱们为了更直观,代码仍是跟规范一一对应吧。须要注意的是,若是promise1
的onRejected
执行成功了,promise2
应该被resolve
。改造代码以下:if(this.status === REJECTED) { var promise2 = new MyPromise(function(resolve, reject) { try { if(typeof onRejected !== 'function') { reject(that.reason); } else { realOnRejected(that.reason); resolve(); } } catch (error) { reject(error); } }); return promise2; }
onFulfilled
或者 onRejected
返回一个值 x
,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
。这条其实才是规范的第一条,由于他比较麻烦,因此我将它放到了最后。前面咱们代码的实现,其实只要onRejected
或者onFulfilled
成功执行了,咱们都要resolve promise2
。多了这条,咱们还须要对onRejected
或者onFulfilled
的返回值进行判断,若是有返回值就要进行 Promise 解决过程。咱们专门写一个方法来进行Promise 解决过程。前面咱们代码的实现,其实只要onRejected
或者onFulfilled
成功执行了,咱们都要resolve promise2
,这个过程咱们也放到这个方法里面去吧,因此代码变为下面这样,其余地方相似:if(this.status === FULFILLED) { var promise2 = new MyPromise(function(resolve, reject) { try { if (typeof onFulfilled !== 'function') { resolve(that.value); } else { var x = realOnFulfilled(that.value); resolvePromise(promise2, x, resolve, reject); // 调用Promise 解决过程 } } catch (error) { reject(error); } }); return promise2; }
如今咱们该来实现resolvePromise
方法了,规范中这一部分较长,我就直接把规范做为注释写在代码里面了。
function resolvePromise(promise, x, resolve, reject) { // 若是 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise // 这是为了防止死循环 if (promise === x) { return reject(new TypeError('The promise and the return value are the same')); } if (x instanceof MyPromise) { // 若是 x 为 Promise ,则使 promise 接受 x 的状态 // 也就是继续执行x,若是执行的时候拿到一个y,还要继续解析y // 这个if跟下面判断then而后拿到执行其实重复了,无关紧要 x.then(function (y) { resolvePromise(promise, y, resolve, reject); }, reject); } // 若是 x 为对象或者函数 else if (typeof x === 'object' || typeof x === 'function') { // 这个坑是跑测试的时候发现的,若是x是null,应该直接resolve if (x === null) { return resolve(x); } try { // 把 x.then 赋值给 then var then = x.then; } catch (error) { // 若是取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise return reject(error); } // 若是 then 是函数 if (typeof then === 'function') { var called = false; // 将 x 做为函数的做用域 this 调用之 // 传递两个回调函数做为参数,第一个参数叫作 resolvePromise ,第二个参数叫作 rejectPromise // 名字重名了,我直接用匿名函数了 try { then.call( x, // 若是 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y) function (y) { // 若是 resolvePromise 和 rejectPromise 均被调用, // 或者被同一参数调用了屡次,则优先采用首次调用并忽略剩下的调用 // 实现这条须要前面加一个变量called if (called) return; called = true; resolvePromise(promise, y, resolve, reject); }, // 若是 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise function (r) { if (called) return; called = true; reject(r); }); } catch (error) { // 若是调用 then 方法抛出了异常 e: // 若是 resolvePromise 或 rejectPromise 已经被调用,则忽略之 if (called) return; // 不然以 e 为据因拒绝 promise reject(error); } } else { // 若是 then 不是函数,以 x 为参数执行 promise resolve(x); } } else { // 若是 x 不为对象或者函数,以 x 为参数执行 promise resolve(x); } }
onFulfilled
和 onRejected
的执行时机在规范中还有一条:onFulfilled
和 onRejected
只有在执行环境堆栈仅包含平台代码时才可被调用。这一条的意思是实践中要确保 onFulfilled
和 onRejected
方法异步执行,且应该在 then
方法被调用的那一轮事件循环以后的新执行栈中执行。因此在咱们执行onFulfilled
和 onRejected
的时候都应该包到setTimeout
里面去。
// 这块代码在then里面 if(this.status === FULFILLED) { var promise2 = new MyPromise(function(resolve, reject) { // 这里加setTimeout setTimeout(function() { try { if (typeof onFulfilled !== 'function') { resolve(that.value); } else { var x = realOnFulfilled(that.value); resolvePromise(promise2, x, resolve, reject); } } catch (error) { reject(error); } }, 0); }); return promise2; } if(this.status === REJECTED) { var promise2 = new MyPromise(function(resolve, reject) { // 这里加setTimeout setTimeout(function() { try { if(typeof onRejected !== 'function') { reject(that.reason); } else { var x = realOnRejected(that.reason); resolvePromise(promise2, x, resolve, reject); } } catch (error) { reject(error); } }, 0); }); return promise2; } if (this.status === PENDING) { var promise2 = new MyPromise(function (resolve, reject) { that.onFulfilledCallbacks.push(function () { // 这里加setTimeout setTimeout(function () { try { if (typeof onFulfilled !== 'function') { resolve(that.value); } else { var x = realOnFulfilled(that.value); resolvePromise(promise2, x, resolve, reject); } } catch (error) { reject(error); } }, 0); }); that.onRejectedCallbacks.push(function () { // 这里加setTimeout setTimeout(function () { try { if (typeof onRejected !== 'function') { reject(that.reason); } else { var x = realOnRejected(that.reason); resolvePromise(promise2, x, resolve, reject); } } catch (error) { reject(error); } }, 0) }); }); return promise2; }
咱们使用Promise/A+官方的测试工具promises-aplus-tests来对咱们的MyPromise
进行测试,要使用这个工具咱们必须实现一个静态方法deferred
,官方对这个方法的定义以下:
deferred
: 返回一个包含{ promise, resolve, reject }的对象
promise
是一个处于pending
状态的promise
resolve(value)
用value
解决上面那个promise
reject(reason)
用reason
拒绝上面那个promise
咱们实现代码以下:
MyPromise.deferred = function() { var result = {}; result.promise = new MyPromise(function(resolve, reject){ result.resolve = resolve; result.reject = reject; }); return result; }
而后用npm将promises-aplus-tests
下载下来,再配置下package.json就能够跑测试了:
{ "devDependencies": { "promises-aplus-tests": "^2.1.2" }, "scripts": { "test": "promises-aplus-tests MyPromise" } }
在跑测试的时候发现一个坑,在resolvePromise
的时候,若是x是null
,他的类型也是object
,是应该直接用x来resolve的,以前的代码会走到catch
而后reject
,因此须要检测下null
:
// 这个坑是跑测试的时候发现的,若是x是null,应该直接resolve if(x === null) { return resolve(x); }
这个测试总共872用例,咱们写的Promise完美经过了全部用例:
在ES6的官方Promise还有不少API,好比:
Promise.resolvePromise.reject
Promise.all
Promise.race
Promise.prototype.catch
Promise.prototype.finally
Promise.allSettled
虽然这些都不在Promise/A+里面,可是咱们也来实现一下吧,加深理解。其实咱们前面实现了Promise/A+再来实现这些已是小菜一碟了,由于这些API所有是前面的封装而已。
将现有对象转为Promise对象,若是 Promise.resolve 方法的参数,不是具备 then 方法的对象(又称 thenable 对象),则返回一个新的 Promise 对象,且它的状态为fulfilled。
MyPromise.resolve = function(parameter) { if(parameter instanceof MyPromise) { return parameter; } return new MyPromise(function(resolve) { resolve(parameter); }); }
返回一个新的Promise实例,该实例的状态为rejected。Promise.reject方法的参数reason,会被传递给实例的回调函数。
MyPromise.reject = function(reason) { return new MyPromise(function(resolve, reject) { reject(reason); }); }
该方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
Promise.all()
方法接受一个数组做为参数,p1
、p2
、p3
都是 Promise 实例,若是不是,就会先调用Promise.resolve
方法,将参数转为 Promise 实例,再进一步处理。当p1, p2, p3所有resolve,大的promise才resolve,有任何一个reject,大的promise都reject。
MyPromise.all = function(promiseList) { var resPromise = new MyPromise(function(resolve, reject) { var count = 0; var result = []; var length = promiseList.length; if(length === 0) { return resolve(result); } promiseList.forEach(function(promise, index) { MyPromise.resolve(promise).then(function(value){ count++; result[index] = value; if(count === length) { resolve(result); } }, function(reason){ reject(reason); }); }); }); return resPromise; }
用法:
const p = Promise.race([p1, p2, p3]);
该方法一样是将多个 Promise 实例,包装成一个新的 Promise 实例。上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。
MyPromise.race = function(promiseList) { var resPromise = new MyPromise(function(resolve, reject) { var length = promiseList.length; if(length === 0) { return resolve(); } else { for(var i = 0; i < length; i++) { MyPromise.resolve(promiseList[i]).then(function(value) { return resolve(value); }, function(reason) { return reject(reason); }); } } }); return resPromise; }
Promise.prototype.catch
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。
MyPromise.prototype.catch = function(onRejected) { this.then(null, onRejected); }
finally
方法用于指定无论 Promise 对象最后状态如何,都会执行的操做。该方法是 ES2018 引入标准的。
MyPromise.prototype.finally = function(fn) { return this.then(function(value){ return MyPromise.resolve(value).then(function(){ return value; }); }, function(error){ return MyPromise.resolve(reason).then(function() { throw error }); }); }
该方法接受一组 Promise 实例做为参数,包装成一个新的 Promise 实例。只有等到全部这些参数实例都返回结果,不论是fulfilled
仍是rejected
,包装实例才会结束。该方法由 ES2020 引入。该方法返回的新的 Promise 实例,一旦结束,状态老是fulfilled
,不会变成rejected
。状态变成fulfilled
后,Promise 的监听函数接收到的参数是一个数组,每一个成员对应一个传入Promise.allSettled()
的 Promise 实例的执行结果。
MyPromise.allSettled = function(promiseList) { return new MyPromise(function(resolve){ var length = promiseList.length; var result = []; var count = 0; if(length === 0) { return resolve(result); } else { for(var i = 0; i < length; i++) { (function(i){ var currentPromise = MyPromise.resolve(promiseList[i]); currentPromise.then(function(value){ count++; result[i] = { status: 'fulfilled', value: value } if(count === length) { return resolve(result); } }, function(reason){ count++; result[i] = { status: 'rejected', reason: reason } if(count === length) { return resolve(result); } }); })(i) } } }); }
彻底版的代码较长,这里若是看不清楚的能够去个人GitHub上看:
// 先定义三个常量表示状态 var PENDING = 'pending'; var FULFILLED = 'fulfilled'; var REJECTED = 'rejected'; function MyPromise(fn) { this.status = PENDING; // 初始状态为pending this.value = null; // 初始化value this.reason = null; // 初始化reason // 构造函数里面添加两个数组存储成功和失败的回调 this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; // 存一下this,以便resolve和reject里面访问 var that = this; // resolve方法参数是value function resolve(value) { if (that.status === PENDING) { that.status = FULFILLED; that.value = value; // resolve里面将全部成功的回调拿出来执行 that.onFulfilledCallbacks.forEach(callback => { callback(that.value); }); } } // reject方法参数是reason function reject(reason) { if (that.status === PENDING) { that.status = REJECTED; that.reason = reason; // resolve里面将全部失败的回调拿出来执行 that.onRejectedCallbacks.forEach(callback => { callback(that.reason); }); } } try { fn(resolve, reject); } catch (error) { reject(error); } } function resolvePromise(promise, x, resolve, reject) { // 若是 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise // 这是为了防止死循环 if (promise === x) { return reject(new TypeError('The promise and the return value are the same')); } if (x instanceof MyPromise) { // 若是 x 为 Promise ,则使 promise 接受 x 的状态 // 也就是继续执行x,若是执行的时候拿到一个y,还要继续解析y // 这个if跟下面判断then而后拿到执行其实重复了,无关紧要 x.then(function (y) { resolvePromise(promise, y, resolve, reject); }, reject); } // 若是 x 为对象或者函数 else if (typeof x === 'object' || typeof x === 'function') { // 这个坑是跑测试的时候发现的,若是x是null,应该直接resolve if (x === null) { return resolve(x); } try { // 把 x.then 赋值给 then var then = x.then; } catch (error) { // 若是取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise return reject(error); } // 若是 then 是函数 if (typeof then === 'function') { var called = false; // 将 x 做为函数的做用域 this 调用之 // 传递两个回调函数做为参数,第一个参数叫作 resolvePromise ,第二个参数叫作 rejectPromise // 名字重名了,我直接用匿名函数了 try { then.call( x, // 若是 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y) function (y) { // 若是 resolvePromise 和 rejectPromise 均被调用, // 或者被同一参数调用了屡次,则优先采用首次调用并忽略剩下的调用 // 实现这条须要前面加一个变量called if (called) return; called = true; resolvePromise(promise, y, resolve, reject); }, // 若是 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise function (r) { if (called) return; called = true; reject(r); }); } catch (error) { // 若是调用 then 方法抛出了异常 e: // 若是 resolvePromise 或 rejectPromise 已经被调用,则忽略之 if (called) return; // 不然以 e 为据因拒绝 promise reject(error); } } else { // 若是 then 不是函数,以 x 为参数执行 promise resolve(x); } } else { // 若是 x 不为对象或者函数,以 x 为参数执行 promise resolve(x); } } MyPromise.prototype.then = function (onFulfilled, onRejected) { // 若是onFulfilled不是函数,给一个默认函数,返回value // 后面返回新promise的时候也作了onFulfilled的参数检查,这里能够删除,暂时保留是为了跟规范一一对应,看得更直观 var realOnFulfilled = onFulfilled; if (typeof realOnFulfilled !== 'function') { realOnFulfilled = function (value) { return value; } } // 若是onRejected不是函数,给一个默认函数,返回reason的Error // 后面返回新promise的时候也作了onRejected的参数检查,这里能够删除,暂时保留是为了跟规范一一对应,看得更直观 var realOnRejected = onRejected; if (typeof realOnRejected !== 'function') { realOnRejected = function (reason) { throw reason; } } var that = this; // 保存一下this if (this.status === FULFILLED) { var promise2 = new MyPromise(function (resolve, reject) { setTimeout(function () { try { if (typeof onFulfilled !== 'function') { resolve(that.value); } else { var x = realOnFulfilled(that.value); resolvePromise(promise2, x, resolve, reject); } } catch (error) { reject(error); } }, 0); }); return promise2; } if (this.status === REJECTED) { var promise2 = new MyPromise(function (resolve, reject) { setTimeout(function () { try { if (typeof onRejected !== 'function') { reject(that.reason); } else { var x = realOnRejected(that.reason); resolvePromise(promise2, x, resolve, reject); } } catch (error) { reject(error); } }, 0); }); return promise2; } // 若是仍是PENDING状态,将回调保存下来 if (this.status === PENDING) { var promise2 = new MyPromise(function (resolve, reject) { that.onFulfilledCallbacks.push(function () { setTimeout(function () { try { if (typeof onFulfilled !== 'function') { resolve(that.value); } else { var x = realOnFulfilled(that.value); resolvePromise(promise2, x, resolve, reject); } } catch (error) { reject(error); } }, 0); }); that.onRejectedCallbacks.push(function () { setTimeout(function () { try { if (typeof onRejected !== 'function') { reject(that.reason); } else { var x = realOnRejected(that.reason); resolvePromise(promise2, x, resolve, reject); } } catch (error) { reject(error); } }, 0) }); }); return promise2; } } MyPromise.deferred = function () { var result = {}; result.promise = new MyPromise(function (resolve, reject) { result.resolve = resolve; result.reject = reject; }); return result; } MyPromise.resolve = function (parameter) { if (parameter instanceof MyPromise) { return parameter; } return new MyPromise(function (resolve) { resolve(parameter); }); } MyPromise.reject = function (reason) { return new MyPromise(function (resolve, reject) { reject(reason); }); } MyPromise.all = function (promiseList) { var resPromise = new MyPromise(function (resolve, reject) { var count = 0; var result = []; var length = promiseList.length; if (length === 0) { return resolve(result); } promiseList.forEach(function (promise, index) { MyPromise.resolve(promise).then(function (value) { count++; result[index] = value; if (count === length) { resolve(result); } }, function (reason) { reject(reason); }); }); }); return resPromise; } MyPromise.race = function (promiseList) { var resPromise = new MyPromise(function (resolve, reject) { var length = promiseList.length; if (length === 0) { return resolve(); } else { for (var i = 0; i < length; i++) { MyPromise.resolve(promiseList[i]).then(function (value) { return resolve(value); }, function (reason) { return reject(reason); }); } } }); return resPromise; } MyPromise.prototype.catch = function (onRejected) { this.then(null, onRejected); } MyPromise.prototype.finally = function (fn) { return this.then(function (value) { return MyPromise.resolve(fn()).then(function () { return value; }); }, function (error) { return MyPromise.resolve(fn()).then(function () { throw error }); }); } MyPromise.allSettled = function (promiseList) { return new MyPromise(function (resolve) { var length = promiseList.length; var result = []; var count = 0; if (length === 0) { return resolve(result); } else { for (var i = 0; i < length; i++) { (function (i) { var currentPromise = MyPromise.resolve(promiseList[i]); currentPromise.then(function (value) { count++; result[i] = { status: 'fulfilled', value: value } if (count === length) { return resolve(result); } }, function (reason) { count++; result[i] = { status: 'rejected', reason: reason } if (count === length) { return resolve(result); } }); })(i) } } }); } module.exports = MyPromise;
至此,咱们的Promise就简单实现了,只是咱们不是原生代码,不能作成微任务,若是必定要作成微任务的话,只能用其余微任务API模拟,好比MutaionObserver
或者process.nextTick
。下面再回顾下几个要点:
then
方法对于还在pending
的任务,实际上是将回调函数onFilfilled
和onRejected
塞入了两个数组resolve
方法会将数组onFilfilledCallbacks
里面的方法所有拿出来执行,这里面是以前then方法塞进去的成功回调reject
方法会将数组onRejectedCallbacks
里面的方法所有拿出来执行,这里面是以前then方法塞进去的失败回调then
方法会返回一个新的Promise以便执行链式调用catch
和finally
这些实例方法都必须返回一个新的Promise实例以便实现链式调用文章的最后,感谢你花费宝贵的时间阅读本文,若是本文给了你一点点帮助或者启发,请不要吝啬你的赞和GitHub小星星,你的支持是做者持续创做的动力。
做者博文GitHub项目地址: https://github.com/dennis-jiang/Front-End-Knowledges