实现 Promise A+ 的 Promise
Promise 专为异步而生,ES6 之前我们写的异步方法各式各样,没有一个统一的规范,直到 Promise 的出现,我们的异步代码经过 Promise 的封装,妙不可言。废话不多说今天从零开始实现一个符合 Promise A+ 规范的 Promise
参考的规范:https://promisesaplus.com/
简单地 sync Promise
每次只能返回一个结果 失败或成功 或 throw Error 被 reject 捕获(Promise 有容错机制)。Promise 内的同步代码会在 new 时都去执行。
有以下需求:
let promise = new Promise((resolve, reject) => {
resolve(1)
reject('2');
throw new Error('3');
});
promise.then((res) => {
console.log('res', res)
}, err => {
console.log('err', err);
})
那下面来实现一个简单地 Promise
Promise 是一个类,它的实例有两个阶段,分别对应不同的状态:
- 未决阶段(unsettled):状态为 pending,表示等待态
- 已决阶段(settled):状态为 fulfilled 或 rejected,就是有返回结果了,表示成功态或失败态
Promise 在 new 时执行 Promise 的 constructor 方法,将参数(函数)在内部执行一遍,所以同步代码都会在此时执行掉,当然可能会出错,所以需要通过 try,catch 容错处理。
const PENDING = "PENDING"; // 等待态
const FULFILLED = "FULFILLED"; // 成功态
const REJECTED = "REJECTED"; // 失败态
class Promise{
constructor(executor) {
this.status = PENDING; // 默认等待态
this.value = undefined; // 成功的返回结果
this.reason = undefined; // 失败返回的结果
let resolve = (value) => { // 定义new时默认的 resolve
if(this.status = PENDING) {
this.value = value;
this.status = FULFILLED;
}
}
let reject = (reason) => { // 定义new时默认的 resolve
if(this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
}
try{
executor(resolve, reject); // 执行new Promise里的回调,回调可能会有错误,需要reject捕获
}catch(e) {
reject(e);
}
}
then(onFulfilled, onRejected) { // 接受推送的结果 失败态 或 成功态
if(this.status === FULFILLED) {
onFulfilled(this.value);
}
if(this.status === REJECTED) {
onRejected(this.reason);
}
}
}
module.exports = Promise;
说明:
1、executor:new Promise 用户传入的回调
2、promise构造器里定义了 resolve、reject,这两个方法只在 executor 内使用了
3、executor 执行会返回一个状态,前题是当前 status 状态为 pending 等待态,然后将返回的结果保存在 this.value或this.reason
4、用户 then 操作,会判断当前的状态,并调用对应用户then时传的回调
这就是个最简单地 Promise,那当 new 一个 Promise 时执行了异步方法,又该如何呢?
处理 async Promise
比如用户加了一个异步操作,还将同一个 Promise 实例 then 了多次
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('222');
}, 1000)
});
promise.then((res) => {
console.log('res', res)
}, err => {
console.log('err', err);
})
promise.then((res) => {
console.log('res', res)
}, err => {
console.log('err', err);
})
有异步操作还被多个 then 执行,这就不难想到要用发布定阅的模式。promise.then 是同步代码,参数为成功和失败的回调,所以 promise.then 执行时 promise 的状态还是等待态 ,这时我们需要将 promise.then 执行的这些回调存储在数组中,等到异步完成返回了状态,再依次循环执行用户 then 时定义的回调。逻辑是这个逻辑,看下实现
const PENDING = "PENDING"; // 等待态
const FULFILLED = "FULFILLED"; // 成功态
const REJECTED = "REJECTED"; // 失败态
class Promise{
constructor(executor) {
this.status = PENDING; // 默认等待态
this.value = undefined; // 成功的返回结果
this.reason = undefined; // 失败返回的结果
// 发布订阅多次then
this.onResolveCbs = []; // 存储成功态时的回调
this.onRejectCBS = []; // 存储失败态时的回调
let resolve = (value) => {
if(this.status = PENDING) {
this.value = value;
this.status = FULFILLED;
this.onResolveCbs.forEach(fn => fn()); // 发布 异步返回成功结果
}
}
let reject = (reason) => {
if(this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
this.onRejectCBS.forEach(fn => fn()); // 发布 异步返回失败结果
}
}
try{
executor(resolve, reject); // 执行new Promise里的回调,回调可能会有错误,需要reject捕获
}catch(e) {
reject(e);
}
}
then(onFulfilled, onRejected) { // 接受推送的结果 失败态 或 成功态 或 等待态
if(this.status === RESOLVE) {
onFulfilled(this.value);
}
if(this.status === REJECT) {
onRejected(this.reason);
}
if(this.status === PENDING) { // 为此状态时,当前Promise有异步操作 Promise.then是同步调用
// 将成功和失败的回调都存到数组中 , 订阅模式
this.onResolveCbs.push(() => {
onFulfilled(this.value);
})
this.onRejectCBS.push(() => {
onRejected(this.reason);
})
}
}
}
module.exports = Promise;
这里就不说明了,代码很详细了,就是一个发布订阅模式
promise.then 的链式调用
then 的链式调用每次都会返回一个成功态或失败态的 promise,那怎样的结果是失败态或成功态呢?
- 失败态:返回一个失 败的 promise、抛出异常
- 成功态:返回一个成功的 promise、普通值
首先,链式调用返回新的 promise 是 Promise 内部返回的,而用户定义返回的 promise 被 Promise 内部处理掉了,说人话就是 then 扔给下一个 then 的结果一个 js 值,不是一个 promise
在者,用户 then 时 Promise 内部直接创建一个 Promise 实例,然后返回,实现了 Promise 的链式调用,当然中间的有很多处理,列举一下
1、需要判断用户 promise.then(resolve, reject) 返回的是一个普通值还是一个 promise,然后统一使用 resolvePromise 来处理,参数必须传四个
promise2:返回给用户的Promise实例,传它有两个作用:
- 其一,判断用户then返回的promise是自己promise2,这样就直接报错
let promise = new Promise((resolve, reject) => {
resolve('222');
});
let promise2 = promise.then((res) => {
return promise2;
})
- 其二,用户 then 返回可能定义了多个嵌套的Promise,这是需要
resolvePromise递归处理,直到拿到一个普通值let promise = new Promise((resolve, reject) => {
resolve('222');
});
// promise2就是Promise内部返回的promise2
let promise2 = promise.then((res) => {
return new Promise((resolve, reject) => {
resolve(res);
})
})resolve、reject:因为下次用户调 promise2.then 时调用的是 promise2 的 resolve、reject,所以不论内部嵌了多少层 promise,都要保证最后一次用的 resolve 或 reject 是 promise2 的,所以需要递归时一直传下去
x 就是 promise.then 的结果
2、当我们 new 一个 promise2 实例时,promise2 还没生成呢,resolvePromise 就在其内部调用 promise2,这必然报错,所以需要做个异步处理,等 promise2 生成后再调用 resolvePromise
3、onFulfilled、onRejected 是用户定义的回调函数,所以用户可能扔出异常,需要在有这两个方法的位置进行容错处理
4、看看 resolvePromise 做了什么
1、如果 then 返回的 promise 是自己,直接报错,上面有提到
2、判断 x 是否为一个 promise,当 x 是一个对象或函数时,可能为 promise,接着进入下一步,不满足就当做普通值处理
3、拿到 x.then,判断是否为一个函数,当为一个函数时,就只能认为他是一个 promise 了,接着进入下一步,否则当做普通值调用 resolve 处理(注意:x.then 最好取一次,因为 then 是一个 getter,可以被用户使用 Object.defineProperty 等的方法劫持进行切面处理,取一次或多次可能会报错,所以此处需要容错处理)
Object.defineProperty(promise, 'then', {
get() {
throw new Error('xxxx');
}
})4、上一步已经判断 x 是一个 promise 了,这一步就是执行了,执行 then,然后可能 y 也是个 promise 所以需要递归处理 y,直到为普通值为止。
5、called 变量作用:防止用户在 resolve 时又调用 reject,在 reject 回调调用 resolve,或者 reject、resolve 多次重复调用,called 保证 resolvePromise 每次调用只执行一个 resolve 或者 reject
下面是具体实现
const PENDING = "PENDING"; // 等待态
const FULFILLED = "FULFILLED"; // 成功态
const REJECTED = "REJECTED"; // 失败态
// 出力返回成功态的结果
function resolvePromise(promise2, x, resolve, reject) {
// 1、如果then返回的promise是自己,直接报错
if(promise2 === x) {
return reject(new TypeError('TypeError: Chaining cycle detected for promise #<Promise>'));
}
let called=false; // 防止在当前promise resolve又调用reject reject又调用resolve
// 2、判断x,当x为对象或Function时,x可能是一个promise
if((typeof x === 'object' && x !== null) || typeof x === 'function') {
try{
let then = x.then; // then只拿一次,如果有人设置then的getter,多次取可能会报错
if(typeof then === 'function') { // 就认为x是一个promise,那就执行then
then.call(x, (y) => {
if(called) return;
called = true;
resolvePromise(promise2, y, resolve, reject); // y可能又是个promise,递归y处理返回的promise
}, (r) => {
if(called) return;
called = true;
reject(r);
})
} else {
resolve(x);
}
}catch (e) {
if(called) return;
called = true;
reject(e)
}
}else {
resolve(x)
}
}
class Promise{
constructor(executor) {
// ......
}
then(onFulfilled, onRejected) { // 接受状态推送的结果 失败态 或 成功态 或 等待态
// promise2链式调用then时返回的Promise
// 若用户then返回的是个promise时,用户定义的promise会被我们拿到进行后续处理,返回给用户的是我们定义的新的promise2
let promise2 = new Promise((resolve, reject) => {// resolve,reject用户链式调用定义的回调
// then方法的回调执行可能是一个普通值或是一个Promise,当然也可能报错
if(this.status === RESOLVE) {
// promise2要传给resolvePromise方法,所以得在promise2 new完之后才能有promise,使用异步setTimeout
setTimeout(() => {
try{
// x 是 onFulfilled 返回的成功态结果(普通值|Promise|throw new Error),可能会报错(new Error)
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject); // 执行到这说明 onFulfilled 执行未报错
}catch(e) {
reject(e);
}
}, 0);
}
if(this.status === REJECT) {
setTimeout(() => {
try {
// 失败态的回调,返回的可能为普通值或Promise或错误
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if(this.status === PENDING) { // 为此状态时,当前Promise有异 步操作 Promise.then是同步调用
// 将成功和失败的回调都存到数组中 , 订阅模式
this.onResolveCbs.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
this.onRejectCBS.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
})
}
})
return promise2;
}
}
module.exports = Promise;
那执行 promise.then 里面有异步操作且有嵌套 promise 呢,异步的标志就是当前 promise 的状态为等待态,只要是等待态那就会有异步方法,也是放到数组里,等异步操作完成后,由对应的状态,调用 onfulilled 或 onrejected,然后会判断返回的是否为 promise。若是 promise 就调用resolvePromise,将 promise2 和用户下次用 promise2.then 定义的 resolve、reject 穿入,保证最终调用的时 promise2.then 定义的resolve、reject。
let promise = new Promise((resolve, reject) => {
resolve('222');
});
promise.then((res) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(new Promise(resolve, reject) => {
// 这里resolve的结果‘111’会被Promise内部拿到,然后调用内部返回的promise的默认的resolve方法,改变当前状态为成功态,值赋给‘this.value’, 等到下次then时就能将成功的结果返回给用户
resolve('1111');
});
}, 1000);
})
// return promise2;
}).then((res) => {
console.log(res);
})
细节处理
首先明确一点,在用 new 操作符创建 Promise 实例时,Promise 构造器会自定义 resolve 和 reject 方法,用来改变当前 promise 的状态 status,以及对应状态的结果推送,promise.then 时调用对应状态的回调。
1、当有以下操作时
let promise = new Promise((resolve, reject) => {
resolve('222');
});
promise.then().then().then(res => {
console.log(res); // 222
})
所以需要判断 promise.then() 执行所传的两个状态回调(resolve,reject),是否为函数,不是函数时就创建一个函数,将上一次 then 的返回结果向下扔
......
then(onFulfilled, onRejected) {
// onFulfilled | onRejected 用户定义的可能不是一个函数,只需要拿到上一个promise resolve | reject 的结果,然后往下扔
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : res => res;
onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err};
// ......
}
.......
2、catch方法:就是一个 then 方法,onFulfilled 为空
catch(onRejected) { // 就是一个then
return this.then(null, onRejected);
}
3、new Promise 可能 resolve 也是 Promise,并且是嵌套的
let promise = new Promise((resolve, reject) => {
resolve(new Promise(res => {
resolve(new Promise(res => {
resolve('1111');
}))
}));
});
所以需要修改 Promise 构造器 constructor 的 resolve 方法,需要递归处理 value,直到拿到普通值为止
class Promise {
constroctor(executor) {
// ......
let resolve = (value) => {
if(value instanceof Promise) {
value.then(resolve, reject); // 递归解决resolve为一个promise的情况,直到为普通值为止
return;
}
if(this.status = PENDING) {
this.value = value;
this.status = RESOLVE;
this.onResolveCbs.forEach(fn => fn()); // 发布 异步返回成功结果
}
}
}
}
其它方法
Promise.all
Promise 的静态方法 all,所传promise都返回成功态数据,则走 resolve,有一个失败就 reject
static all(promises) { // promises的成员为普通值或promise
return new Promise((resolve, reject) => {
let arr = [];
let idx = promises.length;// 计数器,判断处理了几个promises
for(let i = 0; i < promises.length; i++) {
let currentValue = promises[i];
Promise.resolve(currentValue).then(data => {
arr[index] = value;
if (--idx === 0) {
resolve(arr);
}
}, err => {
reject(err)
})
}
})
}
使用场景:并发处理异步任务
Promise.allSettled
和 all 类似,区别在于 allSettled 执行有报错时,不会被 reject,而是全部放到返回结果中
function allSettled(promises) {
return new Promise((resolve, reject) => {
const arr = [];
let idx = promises.length;
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
(value) => {
arr[index] = { status: "fulfilled", value };
if (--idx === 0) {
resolve(arr);
}
},
(error) => {
arr[index] = { status: "rejected", reason: error };
if (--idx === 0) {
resolve(arr);
}
}
);
});
});
}
Promise.resolve
有时我们只需要一个成功态的 promise,不需要关心参数是否为 promise。 let promise = Promise.resolve('1111'),实现很简单
static resolve(value) {
return new Promise((resolve, reject) => {
resolve(value)
})
}
Promise.reject
static reject(value) {
return new Promise((resolve, reject) => {
reject(value)
})
}
Promise.race
只要有一个 resolve 或 reject,promise.then 里对应回调就会触发,若 promises 数组里的 promise 都是等待态,那么,then 会一直等待。
场景:一般可以用于异步 Promise 的超时中断
static race(promises) {
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
promise.then(resolve, reject);
});
});
}
Promise.finally
不管成功还是失败,都会走到 finally 中,并且 finally 之后,还可以继续 then。并且 finally 会将值原封不动的传递给后面的 then
Promise.prototype.finally = function (callback) {
return this.then((value) => { // this指向上一个then返回的promise
return Promise.resolve(callback()).then(() => {
return value;
});
}, (err) => {
return Promise.resolve(callback()).then(() => {
throw err;
});
});
}
测试
安装promises-aplus-tests,执行promises-aplus-tests promise.js,执行结束不报红证明符合Promise A+规范
完整代码
const PENDING = "PENDING"; // 等待态
const FULFILLED = "FULFILLED"; // 成功态
const REJECTED = "REJECTED"; // 失败态
//处理 then 回调返回成功态的结果
function resolvePromise(promise2, x, resolve, reject) {
// 1、如果then返回的promise是自己,直接报错
if (promise2 === x) {
return reject(new TypeError('TypeError: Chaining cycle detected for promise #<Promise>'));
}
let called = false; // 防止在当前promise resolve又调用reject reject又调用resolve
// 2、判断x,当x为对象或Function时,x可能是一个promise
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
let then = x.then; // then只拿一次,如果有人设置then的getter,多次取可能会报错
if (typeof then === 'function') { // 就认为x是一个promise,那就执行then
then.call(x, (y) => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject); // y可能又是个promise,递归y处理返回的promise
}, (r) => {
if (called) return;
called = true;
reject(r);
})
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e)
}
} else {
resolve(x)
}
}
class Promise {
constructor(executor) {
this.status = PENDING; // 默认等待态
this.value = undefined; // 成功的返回结果
this.reason = undefined; // 失败返回的结果
// 发布订阅多次then
this.onResolveCbs = [];
this.onRejectCbS = [];
let resolve = (value) => {
if (value instanceof Promise) {
value.then(resolve, reject); // 递归解决resolve为一个promise的情况,直到为普通值为止
return;
}
if (this.status = PENDING) {
this.value = value;
this.status = FULFILLED;
this.onResolveCbs.forEach(fn => fn()); // 发布 异步返回成功结果
}
}
let reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
this.onRejectCbS.forEach(fn => fn()); // 发布 异步返回失败结果
}
}
try {
executor(resolve, reject); // 执行new Promise里的回调,回调可能会有错误,需要reject捕获
} catch (e) {
reject(e);
}
}
// Promise 的静态方法 all,所传promise都返回成功态数据,则走resolve,有一个失败就reject
static all(promises) { // promises的成员为普通值或promise
return new Promise((resolve, reject) => {
let arr = [];
let idx = 0; // 计数器,判断处理了几个promises
let processData = (value, index) => {
arr[index] = value;
if (++idx === promises.length) {
resolve(arr);
}
}
for (let i = 0; i < promises.length; i++) {
let currentValue = promises[i];
if (currentValue instanceof Promise) { // 是否为Promise
currentValue.then(res => {
processData(res, i);
}, reject); // 有一个出错all就会直接返回错误的推送reject
} else {
processData(currentValue, i);
}
}
})
}
static resolve(value) {
return new Promise((resolve, reject) => {
resolve(value)
})
}
static reject(value) { // reject接搜一个promise无意义,他不会等待异步完成在执行
return new Promise((resolve, reject) => {
reject(value)
})
}
static race(promises) {
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
promise.then(resolve, reject);
});
});
}
finally(callback) {
return this.then((value) => { // this指向上一个then返回的promise
return Promise.resolve(callback()).then(() => {
return value;
});
}, (err) => {
return Promise.resolve(callback()).then(() => {
throw err;
});
});
}
then(onFulfilled, onRejected) { // 接受状态推送的结果 失败态 或 成功态 或 等待态
// onFulfilled | onRejected 用户定义的可能不是一个函数,只需要拿到上一个promise resolve | reject 的结果,然后往下扔
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : res => res;
onrejected = typeof onrejected === 'function' ? onrejected : err => {
throw err
};
// promise2链式调用then时返回的Promise
// 若用户then返回的是个promise时,用户定义的promise会被我们拿到进行后续处理,返回给用户的是我们定义的新的promise2
let promise2 = new Promise((resolve, reject) => { // resolve,reject用户链式调用定义的回调
// then方法的回调执行可能是一个普通 值或是一个Promise,当然也可能报错
if (this.status === FULFILLED) {
// promise2要传给resolvePromise方法,所以得在promise2 new完之后才能有promise,使用异步setTimeout
setTimeout(() => {
try {
// x是onFulfilled返回的成功态结果(普通值|Promise|throw new Error),可能会报错(new Error)
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject); // 执行到这说明onFulfilled执行未报错
} catch (e) {
reject(e);
}
}, 0);
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
// 失败态的回调,返回的可能为普通值或Promise或错误
let x = onrejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (this.status === PENDING) { // 为此状态时,当前Promise有异步操作 Promise.then是同步调用
// 将成功和失败的回调都存到数组中 , 订阅模式
this.onResolveCbs.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
this.onRejectCbS.push(() => {
setTimeout(() => {
try {
let x = onrejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
})
}
})
return promise2;
}
catch (onrejected) { // 就是一个then
return this.then(null, onrejected);
}
}
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;
}
module.exports = Promise;
拓展
promisify
将 node api 转为 Promise
function promisify(cb) {
return (...args) => {
return new Promise((resolve, reject) => {
cb(...args, (err, data) => {
if (err) reject(err);
resolve(data);
});
});
};
}
const readFileAsync = promisify(fs.readFile);
readFileAsync("./a.ts", "utf8").then(
(data) => {
console.log("data", data);
},
(err) => {
console.log("error", err);
}
);
promisifyAll
将一个 node 模块,全部转为 Promise
function promisifyAll(module) {
const obj = {};
const keys = Object.keys(module);
keys.forEach((key) => {
obj[key] = promisify(module[key]);
});
return obj;
}
const fsPromise = promisifyAll(fs);
fsPromise.readFile("./a.ts", "utf8").then(
(data) => {
console.log("data", data);
},
(err) => {
console.log("error", err);
}
);