Promise 对象,相信学前端的小伙伴们都对它很熟了,它是一种现在前端异步编程的主流形式,关于它的文章从基础使用到底层原理分析、实现网上是一抓一大把,它也是现在前端面试的高频题目,深度掌握它也成为一个衡量前端能力的标准了。
之前也写过一篇关于 Promise
对象的文章(一文,一定能让你手动实现Promise),不过那会写得比较简单,这次算是来给它补全(来还债了(ー_ー)!!),废话不多说,下面马上就来开始本文的愉快之旅。
这次所写的 Promise
对象是以 Promises/A+ (中文规范)为标准的,ES6
中就是采用了 Promise/A+
规范。
Promise
的规范有很多,如Promise/A
,Promise/B
,Promise/D
以及Promise/A
的升级版Promise/A+
。Promise/A+
并未规范catch
、all
、race
等这些方法,这些方法都是ES6
自己规范的,其实也不难,后面也会一一讲解到。
源码实现
本章尝试以例子的形式来写出整个 Promise
对象的源码,每行代码会备注详细的注解不怕看不懂哦。
基础结构
- 我们以下面基本使用的例子先来引出
Promise
对象的整体结构。
let p = new Promise((resolve, reject) => {
console.log('Start')
resolve('橙某人');
})
p.then(r1 => {
console.log(r1)
}, e1 => {
console.log(e1)
})
console.log('End');
复制代码
通过上面 Promise
对象的基本使用,我们能先定义出它的构造函数、.then(onFulfilled, onRejected)
方法以及还有两个回调函数 resolve()
和 reject()
的定义,当然,它们是形参,名字你可以随便定义。
// 定义构造函数
function myPromise(executor) {
let _this = this; // 保留 this 的指向,防止下面使用过程中 this 指向不明
_this.status = 'pending'; // status有三种状态: pending || fulfilled || rejected
_this.result = undefined; // 保存调用 resolve(result) 方法传递的值
_this.reason = undefined; // 保存调用 reject(reason) 方法传递的值
// 定义 resolve()
function resolve(result) {
if(_this.status === 'pending') {
_this.result = result; // 保存成功的值
_this.status = 'fulfilled'; // 把 Promise 对象的状态改为成功
}
}
// 定义 reject()
function reject(reason) {
if(_this.status === 'pending') {
_this.status = 'rejected'; // 保存失败的值
_this.reason = reason; // 把 Promise 对象的状态改为失败
}
}
// 立即执行 executor(), 执行 executor() 过程可能会出错, 所以要严谨进行try/catch一下, 并且错误结果能给 Promise 捕获
try{
executor(resolve, reject);
}catch(err) {
reject(err)
}
}
// 定义 .then() 方法, 它的参数是可选的, 只要不是函数就忽略即可
myPromise.prototype.then = function (onFulfilled, onRejected) {
if(this.status === 'fulfilled' && typeof onFulfilled === 'function') {
// .then() 调用是一个异步过程
setTimeout(() => {
onFulfilled(this.result);
})
}
if(this.status === 'rejected' && typeof onRejected === 'function') {
setTimeout(() => {
onRejected(this.reason);
})
}
}
复制代码
一个 Promise
会有三种状态,分别是 pending/fulfilled/rejected
,可以简单理解为等待中、成功和失败三种状态,这是基本知识了,就不作过多介绍了。整体过程不是很难,唯一需要注意的是 .then()
方法是一个异步方法,所以我们把它们放在 setTimeout
中去执行。
- 下面我们来调整一下上面的例子:
let p = new Promise((resolve, reject) => {
console.log('Start')
setTimeout(() => {
resolve('橙某人');
})
})
p.then(r1 => {
console.log(r1)
}, e1 => {
console.log(e1)
})
console.log('End');
复制代码
我们异步调用了 resolve('橙某人');
但它的运行结果和上面的一致,来看看如何实现这个异步调用过程。
function myPromise(executor) {
...
_this.onFulfilledCbs = []; // 调用 then(onFulfilled, onRejected) 方法时, 如果 status 为 pending 状态, 说明 resolve() 为异步调用, 所以 Promise 状态还没改变, 则先将 onFulfilled 回调函数存储起来
_this.onRejectedCbs = []; // 调用 then(onFulfilled, onRejected) 方法时, 如果 status 为 pending 状态, 说明 reject() 为异步调用, 所以 Promise 状态还没改变, 则先将 onRejected 回调函数存储起来
function resolve(result) {
if(_this.status === 'pending') {
_this.result = result;
_this.status = 'fulfilled';
// 执行所有 then(onFulfilled, onRejected) 方法的 onFulfilled
while (_this.onFulfilledCbs.length) {
_this.onFulfilledCbs.shift()(); // 按 then() 方法的调用顺序执行并删除
}
}
}
function reject(reason) {
if(_this.status === 'pending') {
_this.reason = reason;
_this.status = 'rejected';
// 执行所有 then(onFulfilled, onRejected) 方法的 onRejected
while (_this.onRejectedCbs.length) {
_this.onRejectedCbs.shift()(); // 按 then() 方法的调用顺序执行并删除
}
}
}
...
}
myPromise.prototype.then = function (onFulfilled, onRejected) {
...
// Promise 对象还是 pending 状态, 则说明 resolve() 或者 reject() 进行了异步调用
if(this.status === 'pending') {
if(typeof onFulfilled === 'function') {
/**
* 之所以 push 一个函数, 主要是为了回调函数能传值.
* 也可以直接 push 函数, 传值交由上面来完成:
* this.onFulfilledCbs.push(onFulfilled);
* _this.onFulfilledCbs.shift()(_this.result)
*/
this.onFulfilledCbs.push(() => {
onFulfilled(this.result);
})
}
if(typeof onRejected === 'function') {
this.onRejectedCbs.push(() => {
onRejected(this.reason);
})
}
}
}
复制代码
其实异步调用 resolve()
或者 reject()
则 .then()
方法会比它们俩先执行,那么 .then()
内部使用的 Promise
对象的状态会是 pending
等待状态,那么我们只能先把 .then(onFulfilled, onRejected)
方法的成功回调和失败回调先存起来,只有当真正调用过 resolve()
或者 reject()
再去把它们拿出来执行即可。
then() 方法
Promise
对象的基本结构不是很难,也比较好理解,它最强大的地方是它支持链式调用,这是最复杂也是最重点的内容,接下来我们来研究研究它的链式调用,上重头戏了。
链式调用
我们一样先看例子:
let p = new Promise((resolve, reject) => {
console.log('Start')
setTimeout(() => {
resolve('橙某人');
})
})
p.then(r1 => {
return r1;
}).then(r2 => {
return r2;
}).then(r3 => {
console.log(r3); // 橙某人
})
console.log('End');
复制代码
由例子上看,我们可以知道每次调用完 then()
方法必然会返回一个 Promise
对象,它才能接下来继续链式调用下去。而且, then()
中能使用 return
把结果继续往后传递下去,基于这两点我们再来改造下 then()
方法。
myPromise.prototype.then = function (onFulfilled, onRejected) {
// 返回一个新的 Promise 对象
return new myPromise((resolve, reject) => {
if(this.status === 'fulfilled' && typeof onFulfilled === 'function') {
setTimeout(() => {
// 执行 onFulfilled or onRejected 方法可能会出现错误, 所以try/catch一下,并把错误往下传递
try{
// 获取到 return 的值
let x = onFulfilled(this.result);
// 调用新的 Promise 对象的 resolve() 把值继续传下去
resolve(x);
}catch(e) {
reject(e);
}
})
}
if(this.status === 'rejected' && typeof onRejected === 'function') {
setTimeout(() => {
try{
let x = onRejected(this.reason);
reject(x);
}catch(e) {
reject(e);
}
})
}
if(this.status === 'pending') {
if(typeof onFulfilled === 'function') {
this.onFulfilledCbs.push(() => {
try{
let x = onFulfilled(this.result);
resolve(x);
}catch(e) {
reject(e);
}
})
}
if(typeof onRejected === 'function') {
this.onRejectedCbs.push(() => {
try{
let x = onRejected(this.reason);
reject(x);
}catch(e) {
reject(e);
}
})
}
}
})
}
复制代码
通过在 then()
中返回一个新的 Promise
对象,就能实现无限链式调用下去了,是不是挺简单;而通过执行 onFulfilled()
或者 onRejected()
方法来获取return
的值,也就是上面代码中的 x
;最后我们通过新的 Promise
对象的 resolve(x)
和 reject(x)
方法就能把值继续往下传啦(-^〇^-)。当然执行 onFulfilled()
或者 onRejected()
可能会出现错误,所以我们都加上了 try/catch
来捕获一下错误。
值穿透
然后,你以为这就完了吗?还没呢,我们再来看个例子:
let p = new myPromise((resolve, reject) => {
console.log('Start')
setTimeout(() => {
resolve('橙某人');
})
})
p
.then()
.then()
.then()
.then(r4 => {
console.log(r4); // 橙某人
})
console.log('End');
复制代码
是不是这种“值穿透”传递也挺神奇的?我们来看看它要如何去实现。
myPromise.prototype.then = function (onFulfilled, onRejected) {
// 解决值穿透
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : result => result;
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}; // onRejected 直接用 throw 抛出错误,这样它会被 try/catch 捕获到; 注意 onRejected 中的错误不能以 return 的形式传递,都是通过 throw 往后抛下去
// 返回一个新的 Promise 对象
...
}
复制代码
其实这个过程就是一个初始化默认值过程,也就是 .then()
会变成 .then(result => result)
,前面我们说过对于 then()
方法的两个参数 onFulfilled
和 onRejected
我们只考虑它们是函数的情况。现在,当它们不是函数的时候,我们就给它们默认值一个函数并且把结果继续往后抛,就能实现所谓的值穿透了。
值类型判断
接下来就是 then()
方法的最后一步了,这一步也挺复杂的,需要对 then(onFulfilled, onRejected)
两个参数 return
的值进行验证,也就是对 x
做一些类型检验。按照 Promises/A+ 标准规范的信息,我们需要对 x
做这以下几个校验:
呃......是不是都看懵逼了,这是图灵社区译的中文文档,应该算是很贴近了,你也可以去看看英语文档,其实应该也差不多,一般官方文档的话语都是那么难以理解的。不过,不用着急,我把测试例子写出来,你可能就好理解很多了。
x
与then()
新返回的Promise
相等。
let p = new Promise((resolve, reject) => {
console.log('Start')
resolve('橙某人');
})
let t = p.then(() => {
return t
})
console.log('End');
复制代码
x
为另一个Promise
对象。
新的 Promise
对象状态处于 fulfilled
或者 rejected
状态。
let p = new Promise((resolve, reject) => {
console.log('Start')
resolve('橙某人');
});
let pNew = new Promise((resolve, reject) => {
resolve('yd');
// reject('yd');
});
p.then(() => {
return pNew
}).then(r => {
console.log(r);
});
console.log('End');
复制代码
新的 Promise
对象状态处于 pending
状态。
let p = new Promise((resolve, reject) => {
console.log('Start')
resolve('橙某人');
})
let pNew = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('yd'); // 1秒后才调用, 让 Promise 处于 pending 状态
}, 1000)
});
p.then(() => {
return pNew
}).then(r => {
console.log(r); // 一秒后才输出
});
console.log('End');
复制代码
第二个测试的两个小例子,结果虽然一样,但第一个的结果是控制台立即就输出了 yd
,但第二个的结果是控制台等了一秒后才输出 yd
。
- 如果 x 为对象或者函数。
let p = new Promise((resolve, reject) => {
console.log('Start')
resolve('橙某人');
})
// x 为对象
p.then(() => {
return {
then: function(resolve, reject) {
console.log('我是对象的then属性', this);
resolve('yd')
}
}
}).then(r => {
console.log(r); // yd
}, e => {
console.log(e)
})
// x 为函数
function fn() {}
fn.then = function(resolve, reject) {
console.log('我是对函数的then属性', this);
resolve('yd')
}
p.then(() => {
return fn;
}).then(r => {
console.log(r); // yd
})
console.log('End');
复制代码
这样子是不是有点像是把对象和函数的 then
属性造了一个新的 Promise
对象呢?这也告诉我们取名字的时候要注意哦,千万不要和关键字有关联(^ω^)。
下面我们先来看看 then()
方法的调整:
myPromise.prototype.then = function (onFulfilled, onRejected) {
...
var p = new myPromise((resolve, reject) => {
if(this.status === 'fulfilled' && typeof onFulfilled === 'function') {
setTimeout(() => {
let x = onFulfilled(this.result);
// 借助 resolvePromise() 统一验证 x
resolvePromise(p, x, resolve, reject);
})
}
if(this.status === 'rejected' && typeof onRejected === 'function') {
setTimeout(() => {
let x = onRejected(this.reason);
resolvePromise(p, x, resolve, reject);
})
}
if(this.status === 'pending') {
if(typeof onFulfilled === 'function') {
this.onFulfilledCbs.push(() => {
let x = onFulfilled(this.result);
resolvePromise(p, x, resolve, reject);
})
}
if(typeof onRejected === 'function') {
this.onRejectedCbs.push(() => {
let x = onRejected(this.reason);
resolvePromise(p, x, resolve, reject);
})
}
}
})
return p;
}
复制代码
因为对 x
验证的部分比较复杂和重复,所以另外写一个 resolvePromise()
方法。
/**
* 验证 x 的类型
* @param {*} p: 新返回的 Promise 对象
* @param {*} x: onFulfilled or onRejected 返回的值
* @param {*} resolve: 新 Promise 对象的 resolve 方法
* @param {*} reject: 新 Promise 对象的 reject 方法
* @returns
*/
function resolvePromise(p, x, resolve, reject) {
// 1. 如果 x 为新返回的 Promise 对象, 则直接抛出错误
if (x != undefined && p === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
// 2. 如果 x 为另一个 myPromise 对象, 也就是显式 return new myPromise()
if (x instanceof myPromise) {
if (x.status === 'pending') { // 如果 x 这个 myPromise 对象处于 pending 状态, 则要等待直 x 被执行或拒绝
x.then(r => {
resolvePromise(p, r, resolve, reject); // 递归等待 x 被执行
}, e => {
reject(e);
})
} else { // 如果 x 这个 myPromise 对象已经处于 fulfilled or rejected 状态, 则直接再次调用 then() 方法!
x.then(resolve, reject); // 这里的意思其实可以理解为, 我们执行了新的 myPromise 对象的 then() 方法, 它的两个回调参数, 就是我们下一个 then() 两个回调的参数.
}
return;
}
// 3. 如果 x 为对象或者函数, 如果不是就是普通的数据类型, 直接通过
var called; // 只调用一次, 防止重复调用
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 如果 x.then 为一个函数, 则我们会执行这个函数, 并且函数内部的 this 指向 x, 执行这个函数内部可能会有错误, 所以需要 try/catch 一下
try {
let then = x.then; // 获取 x 身上的 then() 方法
if (typeof then === 'function') {
then.call(x, function (res) {
if (called) return;
called = true;
// 可能 res 还是一个 promise 对象, 所以需要递归下去判断 res(也就是x) 的类型
resolvePromise(p, res, resolve, reject); // 递归
}, function (err) {
if (called) return;
called = true;
reject(err)
})
} else {
resolve(x); // 普通值, x.then 可能是 x.then = '橙某人
}
} catch (e) {
reject(e)
}
} else {
resolve(x); // 普通值
// 这里为什么直接就调用 resolve() 不考虑 reject(), 因为规定错误不能通过 return 来传递, 只能通过 throw 的形式, 也就是 onRejected 中 return 是无效的
}
}
复制代码
resolvePromise()
方法是比较复杂的,从上面文字的介绍你就应该能感觉到,它里面涉及到了递归的概念,我觉得看懂它的最好方式就是跟着例子慢慢去分析,加油骚年,有什么疑问也欢迎你留言给我,必回。