背景介绍:
传统的异步串行方案多以回调的形式进行,在这种模式中,上一个的输出作为下一个的输入,数量过多就会产生”回调地狱“问题。Promise以链式调用的形式解决了传统“回调地狱”的问题,以更加直观的形式来进行回调。其次,Promise还能很好地解决异步并发问题,比如借助Promise.all方案。最后,Promise的错误处理机制,让错误处理变得十分简单。
Promise的基本概念:
Promise是异步编程的一种解决方案,简单来说,它就是一种容器,里面保存着某个未来才会结束的事件(比如一些异步操作)。它主要有三个状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)。状态的变化,只能是pending到fulfilled或者pending到rejected,反之则不行,即状态一旦转移,不可再改变。
话不多说,接下来我们看看如何实现一个完整的Promise。
Promise的实现思路:
Promise的实现遵循Promises/A+规范,在此基础上,我们还引入了Promise es6的一些实现,比如Promise.all、Promise、race、Promise.prototype.finally等扩展实现。我将整个实现过程分为以下3个步骤:
- Promise的基础框架搭建;
- Promise.then实现(到这里,Promise/A+规范的内容就已经结束了)
- Promise es6扩展实现(Promise.all、Promise、race、Promise.prototype.finally等等)
Pomise的实现:
Promise的基础框架搭建:
- Promise的三种状态pending、fulfilled、rejected;
- 成功、失败的回调;
- Promise.then基础框架。 这部分代码比较简单,我就不过多解释,代码如下:
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
class MyPromise {
constructor(fn) {
this.state = PENDING; //初始状态
const resolve = (value) => {
if (this.state !== PENDING) return;
this.state = RESOLVED;
this.value = value;
}
const reject = (reason) => {
if (this.state !== PENDING) return;
this.state = REJECTED;
this.reason = reason;
}
try {
fn(resolve, reject);
} catch(err) {
reject(err); //调用fn出错时,直接reject
}
}
then(onFulFilledCb, onRejectedCb) {
if (this.state === PENDING) {
//todo...
} else if (this.state === RESOLVED) {
onFulFilledCb(this.value)
} else if (this.state === REJECTED) {
onRejectedCb(this.reason)
}
}
}
Promise.then实现:
基于上面的基础框架,我们来思考一些问题。如果Promise传入的是一个异步的函数,此时在执行到.then回调时状态还是pending态,这时候该怎么办?Promise.then之后是可以继续进行.then的,这里我们该怎么实现?如果在执行成功或者错误的回调时候,又抛错了,又该如何处理呢?等等。结合A+规范,下面我们将Promise.then拆分成4个小步骤:
- 处理Promise传入异步函数,.then状态仍然是pending的情况: 虽然此时不会立即去执行成功或者失败的回调,但是我们想要在接下来状态改变之后就去执行相应的回调。而且,同一个Promise是可以被多次.then的,多次.then的回调按照写入的顺序执行。所以我用俩数组分别去存储成功和失败的回调,在状态改变之后遍历执行即可。并且在执行回调的过程中,如果出现异常,也会被捕获。代码如下:
class MyPromise {
constructor(fn) {
this.state = PENDING; //初始状态
this.onFulfilledCbs = []; //存储成功的回调
this.onRejectedCbs = []; //存储失败的回调
const resolve = (value) => {
if (this.state !== PENDING) return;
this.state = RESOLVED;
this.value = value;
this.onFulfilledCbs.forEach(cb => cb());
}
const reject = (reason) => {
if (this.state !== PENDING) return;
this.state = REJECTED;
this.reason = reason;
this.onRejectedCbs.forEach(cb => cb());
}
try {
fn(resolve, reject);
} catch(err) {
reject(err); //调用fn出错时,直接reject
}
}
then(onFulFilledCb, onRejectedCb) {
if (this.state === PENDING) {
this.onFulfilledCbs.push(() => {
onFulFilledCb(this.value);
})
this.onRejectedCbs.push(() => {
onRejectedCb(this.reason);
})
} else if (this.state === RESOLVED) {
onFulFilledCb(this.value)
} else if (this.state === REJECTED) {
onRejectedCb(this.reason)
}
}
}
2.Promise.then返回的是一个新的Promise,可以被链式调用: 根据A+规范.then必须返回一个新的Promise,而且可以持续的进行then链的调用。.then的回调应该被当作一个函数来调用,即使传入的不是一个函数(这个就是“穿透”的概念,即经过几层then链调用之后,仍然可以在最后拿到之前resolve的值)在执行then回调的过程中如果出现异常,也会被捕获,代码如下:
then(onFulFilledCb, onRejectedCb) {
onFulFilledCb = typeof onFulFilledCb === 'function' ? onFulFilledCb : v => v;
onRejectedCb = typeof onRejectedCb === 'function' ? onRejectedCb : err => {throw err};
return new MyPromise((resolve, reject) => {
if (this.state === PENDING) {
this.onFulfilledCbs.push(() => {
try {
const x = onFulFilledCb(this.value);
resolve(x);
} catch (err) {
reject(err)
}
})
this.onRejectedCbs.push(() => {
try {
const x = onRejectedCb(this.reason);
resolve(x);
} catch (err) {
reject(err);
}
})
} else if (this.state === RESOLVED) {
try {
const x = onFulFilledCb(this.value);
resolve(x);
} catch (err) {
reject(err);
}
} else if (this.state === REJECTED) {
try {
const x = onRejectedCb(this.reason);
resolve(x);
} catch (err) {
reject(err);
}
}
})
}
3.根据规范onResolvedCb和onRejetedCb必须被异步的执行,在事件循环转化之后的一个新的stack中回调才会被调用: 这里读起来有点拗口,A+规范里面就是这么去描述的。被“异步的执行”,这里规范里面指出可以有多种方式,比如宏任务的setTimeout和setImmediate的形式,或者微任务中MutationObserver和 process.nextTick的形式,都可以。es6里面应该是微任务的形式去实现,所以我们常说Promise.then里的执行时机,是以一个微任务的形式就是这么个道理。这里,我们用setTimeout宏任务这样一个比较简单的形式去实现,代码比较简单,就是将两种类型的回调进行setTimeout包裹,这里不再单独贴代码。 4. then传递成功或失败的函数,针对它的返回值会做不同处理。 这是then实现最难也是最复杂的一个地方。根据成功或失败回调函数的返回值x,会有以下情况出现:
- 如果x不是一个Promise,则x会被作为下一个then的成功回调结果;
- 想要走到下一个then的失败回调,那么必须在当前then的回调中抛出一个错误;
- 如果x“是”一个Promise,那么下一个then会走怎样的回调,取决于x这个Promise的状态怎么变化。(为什么这里的是字打了引号,是因为x.then属性存在,且then是一个函数,那么x就被看作是一个Promise来处理)而且,如果x和当前then返回的Promise是同一个则会报错。
在A+规范里面,这个处理过程被称为是Promise Resolution Procedure,出了上面三点,有些细节性的问题,我直接在代码里面会作出注解,以下是代码实现:
const resolutionProcedure = (promise, x, resolve, reject) => {
if (promise === x) {
//如果x和promise2是同一个对象,则永远也走不到下一步,进入一个死循环了
return reject(new TypeError("Chaining cycle detected for promise"));
}
if ((typeof x === "object" && x !== null) || typeof x === "function") {
//规范里面指出了,走了成功或者失败,就不能再走一次成功或者失败,所以用一个变量来保存是否走过成功或者失败。
let isCalled = false;
try {
const then = x.then;
if (typeof then === "function") {
//调用then函数,并且修改其this指向为x
then.call(
x,
(y) => {
if (isCalled) return;
isCalled = true;
//走了成功的回调,此时如果y又是一个Promise,那么我们最终的状态还是取决于y Promise,
//所以对于y值进行递归的resolutionProcedure处理
resolutionProcedure(promise, y, resolve, reject);
},
(r) => {
if (isCalled) return;
isCalled = true;
//走的是失败的回调,与成功的时候对应,但是这里不需要递归处理,
//因为一旦走到reject,不管返回的是不是一个Promise,直接进入到下一个then的reject回调中
reject(r);
}
);
} else {
resolve(x);
}
} catch (err) {
if (isCalled) return;
isCalled = true;
reject(err);
}
} else {
//除去上述情况,都会走到resolve来
resolve(x);
}
};
then(onFulFilledCb, onRejectedCb) {
onFulFilledCb =
typeof onFulFilledCb === "function" ?
onFulFilledCb : (v) => v;
onRejectedCb =
typeof onRejectedCb === "function" ?
onRejectedCb : (err) => {throw err;};
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === PENDING) {
this.onFulfilledCbs.push(() => {
setTimeout(() => {
try {
const x = onFulFilledCb(this.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
})
});
this.onRejectedCbs.push(() => {
setTimeout(() => {
try {
const x = onRejectedCb(this.reason);
resolutionProcedure(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
})
});
}
if (this.state === FULFILLED) {
setTimeout(() => {
try {
const x = onFulFilledCb(this.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
})
}
if (this.state === REJECTED) {
setTimeout(() => {
try {
const x = onRejectedCb(this.reason);
resolutionProcedure(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
})
}
});
return promise2;
}
到这里Promise A+规范的整个实现就算大功高成了,A+规范网站上提供了测试用的库promises-aplus-tests 读者可以自行拷贝代码,然后测试一下。接下来我们再看看关于es6里面提供的Promise的一些相关扩展方法实现。
es6里部分Promise扩展方法实现:
私有属性上面的Promise.resolve、Promise.reject、Promise.all、Promise.race、Promise.prototype.finally实现,直接上代码:
static resolve(value) {
return new MyPromise((resolve) => {
resolve(value);
});
}
static reject(err) {
return new MyPromise(
(null,
(reject) => {
reject(err);
})
);
}
static all(promises) {
return MyPromise((resolve, reject) => {
let counts = 0,
results = [];
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
(res) => {
results[index] = res;
counts++;
if (counts === promises.length) resolve(res);
},
reject
);
});
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach((promise) => {
MyPromise.resolve(promise).then(resolve, reject)
})
})
}
finally(callback) {
const P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
需要注意一点的就是all实现中,不是判断index === promises.length,因为很可能最后一个Promise先完成,但是前面尚未完成,但此时不应该resolve。以及finally,不管成功或者失败都会执行,而且才cb中拿不到关于成功或者失败的返回值,但是在finnaly之后.then中能难道成功或者失败的值。
写在最后:
以上就是本次分享关于Promise实现的全部内容,当然有些代码可以优化,比如then里面多次setTimeout部分。欢迎大家一起讨论哈,好久不写文章,有些生疏,多多包含。要是给个小小的点赞就最好不过了,谢谢大家~。