根据 Promise A+
规范进行实现,并额外实现其 all
、race
等静态方法。
代码的实现完美符合 Promise A+ 规范
Promise / A+ 规范
所谓规范,也就是我们实现promise时,需要遵循的一些规范(当然你也可以不遵循……)。
那么,规范这种东西我懒得写了,promise A+ 具体规范可以看下面:
我们的主要任务还是以代码实现为主!
基本结构
通过原生Promise我们可以知道,Promise 它实际上就是一个构造函数,毕竟只有构造函数才可以使用 new
,那么,我们就以ES6语法来进行我们的代码实现。
首先,把我们Promise这个类的骨架给搭建起来
class Promise { constructor() { } }
|
执行器
Promise接收一个参数,这个参数是一个函数,同时它包含2个形参 resolve
和 reject
。
那么,我们就需要在构造器中接收这个函数。
只要你基础不是很差,应该能看出,resolve
和 reject
是由我们的类进行传递的,也就是说,这2个方法是Promise自己身上的方法
class Promise { constructor(executor) { executor(this.resolve, this.reject); } resolve = () => {} reject = () => {} }
|
在这里,我们做了几件事:
- 接收executor这个执行函数
- 往类中添加resolve和reject两个方法
- 执行executor并传递我们的resolve和reject给它
实现resolve
用过原生promise的都应该知道,resolve是一个函数,且该函数可以接收一个参数,下面是原生Promise的例子
new Promise((resolve, reject) => { resolve(1); }).then(value => { console.log(value); });
|
先不管then,我们先来实现resolve,在实现之前,根据A+规范,我们知道:
promise有三种状态 pending
、fulfilled
、rejected
,分别代表等待中,成功,失败。
而在promise中一旦resolve,就会把promise的状态从pending变为 fulfilled
。
并且,你 resolve(1)
中的参数 1
我也得接收的对吧?我们就用一个变量 value
来接收。resolve的参数是可选的,你可以传,你传了我就接收,你不传我就默认undefined。
最后,我们要知道,resolve的作用是把promise从pending
状态转为 fulfilled
状态。
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected';
class Promise { state = PENDING; value = null; constructor(executor) { executor(this.resolve, this.reject); } resolve = (value) => { if(this.state === PENDING) { this.state = FULFILLED; this.value = value; } } }
|
实现reject
reject的实现与上面的resolve差不多,区别在于,它是将promise的状态从 pending
状态转为 rejected
状态。
同样的,reject也可以接收一个参数,我们就用一个变量 reason
来保存它的参数。
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected';
class Promise { state = PENDING; value = null; reason = null; constructor(executor) { executor(this.resolve, this.reject); } reject = (reason) => { if(this.state === PENDING) { this.state = FULFILLED; this.reason = reason; } } }
|
测试
写到这里,我们先来进行一个小小的测试,看看我们写的代码是否有误。
new Promise((resolve, reject) => { console.log('执行我们自己的promise'); })
|
如果执行上面的代码后能正常输出console的内容,则证明无误。
我们接着往下写。
实现then(重点难点)
在实现之前,我们应该知道几个点:
- then什么时候会被触发
- then有多少个形参
- 假如then有返回值怎么办
我们先来回答上面的三个问题
new Promise((resolve, reject) => { console.log(1); resolve(2); }).then(value => { console.log(value); return 3; }, err => { console.log(err); }).then(value => { console.log(value); })
|
观察上面的代码,我们知道
- 只有在Promise被实例化的同时,执行了resolve,才会触发then
- then有2个形参,分别对应成功(fulfilled)和失败(rejected)两种状态的回调函数
- then里的返回值会作为下一个then成功状态的回调函数参数
最后,我们还得知道,在 A+ 规范中规定了,then的两个形参名字应为:
- onFulfilled:promise状态成功时,参数为当前状态值value或者没有。
- onRejected:promise状态为失败时,参数是失败信息reason或者没有。
基本形态
then方法本质上就是我们Promise类上的一个方法,它接收2个形参,就这样。
class Promise {
then = (onFulfilled, onReject) => { } }
|
onFulfilled
onFulfilled是干嘛的呢?它是,当我们promise当前的状态为成功时,会调用的函数。
它有一个参数value,就是我们 resolve(value) 中的 value
。
then = (onFulfilled, onReject) => { if(this.state === FULFILLED) { onFulfilled(this.value); } }
|
onReject
onReject与onFulfilled差不多,区别在于,只有当我们promise的状态为失败时,才会调用。
它有一个参数,就是我们 reject(reason) 中的 reason
。
then = (onFulfilled, onReject) => { if(this.state === REJECTED) { onReject(this.reason); } }
|
测试
那么,我们来测试一波
new Promise((resolve, reject) => { console.log('value', 1); resolve(2); }).then(value => { console.log('value', value); });
|
观察执行结果,能看出,我们的代码是 完美 实现。
但是,假如我们的promise里涉及到异步操作呢?
new Promise((resolve, reject) => { console.log('value', 1); setTimeout(() => { resolve(2); }, 0); }).then(value => { console.log('value', value); });
|
why! 为什么?我们的 value 1哪去了?
其实是这样的,我们的 setTimeout
是异步代码,因此会被延迟执行,哪怕你写的时间是 0。
随后,js把你的异步代码给丢到一边玩泥巴后,又往后看了看还有没有别的代码,它发现了 then
。
由于我们此刻的then是同步代码,所以它就先执行then了,把then的事情干完了,才去执行我们的setTimeout。
你想想,我then都执行完了,你还resolve有毛用?
异步处理
通过上面的解释说明,我们知道了, 由于then先执行的缘故,导致我们的resolve变得没有了意义,那么我们的解决方案是什么呢?
首先,我们知道,promise的初始状态都是pending
,且只有 resolve 和 reject 可以修改状态。
那么,我 resolve 都还没有执行,此时promise的状态不就是 pending
了嘛,所以呀,我们只需要 在then里处理pending状态 就可以了。
class Promise { onFulfilledCallback = null; onRejectedCallback = null;
then = (onFulfilled, onReject) => { if(this.state === PENDING) { this.onFulfilledCallback = onFulfilled; this.onRejectedCallback = onReject; } } }
|
上面是在 then 中增加了对pending状态的判断,以及增加了2个类属性。
接下来,我们还得再做一件事,我们上面把成功和失败的处理函数保存起来了,那么应该在什么时候,又在哪调用呢?
答案是:resolve 和 reject
resolve = () => { resolve = (value) => { if (this.state === PENDING) { this.onFulfilledCallback && this.onFulfilledCallback(value); } }
reject = (reason) => { if (this.state === PENDING) { this.onRejectedCallback && this.onRejectedCallback(reason); } } }
|
测试
再次进行异步代码测试
new Promise((resolve, reject) => { console.log('value', 1); setTimeout(() => { resolve(2); }, 1500); }).then(value => { console.log('value', value); });
|
可以看到,我们定时器设置了1.5秒,在输出 value 1的时候,等待了1.5秒,随后输出 value 2。
那么,我们的异步处理就搞定了。
到这里,我们的代码就已经开始复杂起来了,不过问题不大,假如你们感觉理解起来还有点晕,先不要往下看,先把上面的知识理清楚,多写多思考几遍就会了。
链式调用
我们先来看原生的promise链式调用例子
new Promise((resolve, reject) => { resolve(1); }).then().then().then().then().....
|
可以看到,可以无限制的往下点出then。
而我们的呢,结果竟是……
new Promise((resolve, reject) => resolve(1)) .then(value => { console.log('value', value); }) .then(value => { console.log('value', value); });
|
在做这个需求之前,我们先捋一捋,先思考个问题,什么东西可以使用 then ?
答:Promise实例对象
因为then本来就是promise类的方法,只有promise的实例对象才拥有 then
那么,如何才能做到链式调用 then 呢?
答:then 每次被调用时,都返回一个Promise的实例对象。
那么我们来实现一下
then = (onFulfilled, onReject) => { let promise2 = new Promise((resolve, reject) => { if (this.state === FULFILLED) { onFulfilled(this.value); }
if (this.state === REJECTED) { onReject(this.reason); }
if (this.state === PENDING) { this.onFulfilledCallback = onFulfilled; this.onRejectedCallback = onReject; } }); return promise2; }
|
再次执行上一轮的测试,会发现,虽然不报错了,但是,我的 第二个 then 似乎没有被执行?
我们把代码改造一下:
class Promise { onFulfilledCallback = []; onRejectedCallback = [];
resolve = (value) => { if (this.state === PENDING) { while (this.onFulfilledCallback.length) { this.onFulfilledCallback.shift()(); } } }
reject = (reason) => { if (this.state === PENDING) { while (this.onRejectedCallback.length) { this.onRejectedCallback.shift()(); } } }
then = (onFulfilled, onReject) => { let promise2 = new Promise((resolve, reject) => { const onFulfilledCallback = () => { setTimeout(() => { const x = onFulfilled(this.value); this.resolvePromise(promise2, x, resolve, reject); }) }
const onRejectedCallback = () => { setTimeout(() => { const x = onReject(this.reason); this.resolvePromise(promise2, x, resolve, reject); }) }
if (this.state === FULFILLED) { onFulfilledCallback(); }
if (this.state === REJECTED) { onRejectedCallback(); }
if (this.state === PENDING) { this.onFulfilledCallback.push(onFulfilledCallback); this.onRejectedCallback.push(onRejectedCallback); } });
return promise2; }
resolvePromise = (promise2, x, resolve, reject) => { if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
} else { resolve(x); } } }
|
那么,上面这一大串代码,我们做了什么呢?
- 保存成功和失败状态处理函数的变量 改为数组
- resolve和reject在最后均通过循环遍历我们的状态处理函数数组,依次取出并执行。
- 把成功处理函数和失败处理函数单独封装,并将其放入定时器内执行。
- 增加了 **resolvePromise ** 这个类方法
为什么要这么做?
每当我们点一次 then,then函数都会被执行,且then的成功和失败处理函数我们也需要处理。
promise.then().then().then().then().then()...
|
这个代码会触发5次 then 方法
而且,上一个 then 的返回值
是下一个 then 的 resolve 参数
我们也说过了,只有promise调用了resolve的时候,才能进入then
那我们是不是返回了一个promise2
?
你返回了一个promise实例对象,可如果你这个实例对象不调用resolve,我能进入下一个 then吗?
不能!
那么怎么判断 then 有没有返回值呢?简单,onFulfilled 和 onRejected 就对应着 then 的2个方法,我可以用个 x
变量来接收。
接收到then的返回值后(如果没有主动return,就是undefined)
我又增加了一个类方法 resolvePromise 来判断,本次then执行结束,应该调用 resolve,还是调用 reject。
在 resolvePromise 中,根据promise A+ 规范,我们需要根据 x 的数据类型,进行不同的操作。
resolve = (promise2, x, resolve, reject) => { if(x !== null && (typeof x === 'object' || typeof x === 'function'))) { } else { resolve(x); } }
|
那为什么我要在定时器里写呢?
因为我们的promise2在执行到fulfilled和rejected的时候,promise2这个变量还没有初始化完毕呢,因此,我们使用定时器,把它推入事件队列中,等到下一轮事件循环的时候,就能拿到初始化后的promise2了。
then = (onFulfilled, onReject) => { let promise2 = new Promise((resolve, reject) => { if(this.state === FULFILLED) { const x = onFulfilled(this.value); this.resolvePromise(promise2, x, resolve, reject); } }); return promise2; }
|
说白了就是这个阶段如果不用定时器来延迟执行时机的话,我们无法拿到promsie2,因为此时的promise2还没有完成初始化,它还是一个 不存在的变量,所以我们需要使用定时器。
到了这时候,我们的promise链式调用功能就完成了。
还是那句话,这一节有点小难,也有点绕,如果没有理解,建议先把这块内容理清楚!
完善resolvePromise
resolvePromise的作用是用于处理then里的返回值,假如你的返回值是普通值、对象、函数,我要怎么处理。
我们需要遵循 promise A+ 规范来实现!
来看下我们当前的代码
resolvePromise = (promise2, x, resolve, reject) => { if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
} else { resolve(x); } }
|
根据 A+ 规范,如果x是对象或者函数,需要声明一个变量then,然后把x.then赋值给它,假如x身上没有then这个属性,则reject这个错误
resolvePromise = (promise2, x, resolve, reject) => { if (x !== null && (typeof x === 'object' || typeof x === 'function')) { let then; try { then = x.then; } catch (err) { reject(err); } } else { resolve(x); } }
|
接下来,我们需要 判断这个变量then是不是一个函数
如果then是一个函数,则
- 修改then的this指向为x
- 传递2个参数(这2个参数其实就是对应then方法的两个形参)
y
: 相当于 then 的resolve方法,被调用时执行 resolvePromiser
: 相当于 then 的reject方法- 如果
y
和 r
都被调用了,或者被调用了多次,则只第一次有效,后面的忽略。 - 如果在调用
then
时抛出了异常,则:- 如果
y
或 r
已经被调用了,则忽略它。 - 否则, 以
e
为reason将 promise
拒绝。
如果then不是函数,则
- 以x为值,调用resolve
代码实现
if (x !== null && (typeof x === 'object' || typeof x === 'function')) { let then; let called = false; try { then = x.then; } catch (err) { reject(err); }
if (typeof then === 'function') { try { then.call( x, y => { if (called) return; called = true; this.resolvePromise(promise2, y, resolve, reject); }, r => { if (called) return; called = true; reject(r); } ) } catch (e) { reject(e); } } else { resolve(x); } } else { resolve(x); }
|
到此为止,我们的resolvePromise方法就完成了
异常处理
为了避免代码执行过程中出现错误,进而导致程序崩溃,我们需要在可能会出现错误的地方进行容错处理
**成功处理函数增加 try/catch **
const onFulfilledCallback = () => { setTimeout(() => { try { const x = onFulfilled(this.value); this.resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }) }
|
**失败处理函数增加 try/catch **
const onRejectedCallback = () => { setTimeout(() => { try { const x = onReject(this.reason); this.resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }) }
|
增加catch方法
在原生promise的实例对象中是有catch这么一个方法的,它一般写在链式调用的最后面,看个例子
new Promise((resolve, reject) => { resolve(1); }) .then(value => { return Promise.reject('这是一个错误信息'); }) .catch(err => { console.log('err', err); })
|
假如then里没有对错误进行捕获处理,则错误信息会被最后的catch给捕获。
我们来实现一下,实现代码也比较简单
catch = (onRejected) => { return this.then(null, onRejected); }
|
其它
then方法的两个参数是可选的,可以传,也可以不传。
我们也来实现一下,使得它们变为可选参数。
注意:遵循promise A+ 规范
then = (onFulfilled, onRejected) => { if (typeof onFulfilled !== 'function') onFulfilled = value => value; if (typeof onRejected !== 'function') onRejected = reason => { throw reason }; }
|
不理解为什么这么做的同孩,需要多看几遍,多分析几遍
到此为止,我们的promise就已经实现完了
我们可以看到,promise的实现中,then方法是最复杂的
但其实,只要你多看几遍,会发现其实也没有想象中那么难
实现静态resolve
我们来看下原生promise的例子
new Promise((resolve, reject) => { resolve(1); }).then(value => { return Promise.resolve(2); }).then(value => { console.log(value); })
|
我们可以看到,上面这个例子中的 resolve
是一个静态方法,返回的是一个成功状态的promise,这样我们才可以继续向后点出then。
那么,我们来实现一下,实现过程还是很简单的。
static resolve = (value) => { return new Promise((resolve, reject) => { resolve(value); }) }
|
对,你没看错,就是这么简单。
接收到用户传递的参数,随后创建一个promise对象,调用该实例对象的resolve(value) 出去,perfect!
实现静态reject
既然有静态resolve,那自然也有静态reject。
实现的过程与静态resolve几乎一样。
static reject = (reason) => { return new Promise((resolve, reject) => { reject(reason); }) }
|
实现静态all
我们看个原生的例子
let promises = [...]; Promise .all(promises) .then(value => { console.log(value); })
|
不了解Promise.all的同学,请自行去百度,这里不会细讲。
promise.all方法通常用来实现并发,例如并发请求等。
同时,它的特性是:所有promise的状态都为成功,才是成功,只要有一个promise是失败的,则整体全部失败。
我们来实现一下
static all = (promises) => { let result = []; let count = 0; if (!Array.isArray(promises)) return promises; return new Promise((resolve, reject) => { const insertData = (data, index) => { result[index] = data; count++; if(count === result.length) { resolve(result); } } for(let i = 0; i < promises.length; i++) { promises[i].then(value => insertData(value, i), reject); } }) }
|
实现静态race
race与all特性差不多,区别在于:
只要有一个promise的状态是成功,整体就是整个,只有所有promise都是失败时,才是失败。
实现代码
static race = (promises) => { if (!Array.isArray(promises)) return promises; return new Promise((resolve, reject) => { for(let i = 0; i < promises.length; i++) { promises[i].then(resolve, reject); } }) }
|
A+规范测试
我们的promise已经全部实现完了,那么,我们来测试下我们的代码,是否符合promise A+ 规范吧
为了进行代码测试,我们需要往我们的Promise类中增加一个静态方法(它只用来做测试,测完可以删除)
static deferred = function () { let result = {}; result.promise = new Promise(function (resolve, reject) { result.resolve = resolve; result.reject = reject; });
return result; }
|
接下来,把我们写的Promise类给导出(commonJS规范)
module.exports = Promise;
|
最后,执行测试,在我们文件所在的目录下,打开命令行工具,输入以下命令
npx promises-aplus-tests .\xxx.js
|
其中的 xxx.js 就是你的文件名,就是我们写的这个Promise文件
我测试了一下,所有代码全部通过了测试,舒服~
完整代码
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected';
class Promise { state = PENDING; value = null; reason = null; onFulfilledCallback = []; onRejectedCallback = [];
constructor(executor) { executor(this.resolve, this.reject); }
resolve = (value) => { if (this.state === PENDING) { this.state = FULFILLED; this.value = value; while (this.onFulfilledCallback.length) { this.onFulfilledCallback.shift()(); } } }
reject = (reason) => { if (this.state === PENDING) { this.state = REJECTED; this.reason = reason; while (this.onRejectedCallback.length) { this.onRejectedCallback.shift()(); } } }
then = (onFulfilled, onRejected) => { if (typeof onFulfilled !== 'function') onFulfilled = value => value; if (typeof onRejected !== 'function') onRejected = reason => { throw reason };
let promise2 = new Promise((resolve, reject) => { const onFulfilledCallback = () => { setTimeout(() => { try { const x = onFulfilled(this.value); this.resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }) }
const onRejectedCallback = () => { setTimeout(() => { try { const x = onRejected(this.reason); this.resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }) }
if (this.state === FULFILLED) { onFulfilledCallback(); }
if (this.state === REJECTED) { onRejectedCallback(); }
if (this.state === PENDING) { this.onFulfilledCallback.push(onFulfilledCallback); this.onRejectedCallback.push(onRejectedCallback); } });
return promise2; }
resolvePromise = (promise2, x, resolve, reject) => { if (promise2 === x) reject(new TypeError(`循环引用错误`)); if (x !== null && (typeof x === 'object' || typeof x === 'function')) { let then; let called = false; try { then = x.then; } catch (err) { reject(err); }
if (typeof then === 'function') { try { then.call( x, y => { if (called) return; called = true; this.resolvePromise(promise2, y, resolve, reject); }, r => { if (called) return; called = true; reject(r); } ) } catch (e) { if (called) return; reject(e); } } else { resolve(x); } } else { resolve(x); } }
static resolve = (value) => { return new Promise((resolve, reject) => { resolve(value); }) }
static reject = (reason) => { return new Promise((resolve, reject) => { reject(reason); }) }
static all = (promises) => { let result = []; let count = 0;
if (!Array.isArray(promises)) return promises;
return new Promise((resolve, reject) => { const insertData = (data, index) => { result[index] = data; count++; if (count === result.length) { resolve(result); } }
for (let i = 0; i < promises.length; i++) { promises[i].then(value => insertData(value, i), reject); } }) }
static race = (promises) => { if (!Array.isArray(promises)) return promises; return new Promise((resolve, reject) => { for (let i = 0; i < promises.length; i++) { promises[i].then(resolve, reject); } }) } }
|