跳到主要内容

实现 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);
}
);