背景
经常用Promise完成前端web请求,如处理fetchAPI封装的一些网络请求结果时候会调用Promise的相关API,它的出现是为了解决回调地狱问题,但是Promise的内部是如何实现的呢?带着这个疑问,也是一道面试题,有了这篇文章
Promise的三种状态
Promise对象的构造语法如下:
let promise = new Promise(function(resolve, reject) {
// executor 函数体
});
传递给Promise构造函数的function称为executor,当Promise对象被创建的时候,它会立即调用;
其内部其实就是一个状态机,有三种状态pending、fulfilled、rejected,分别对应着等待、成功、拒绝
从状态图变化可以看出,promise的executor函数只会调用resolve或reject,promise的最后状态一定变化,而且只有返回结果或返回错误一种可能。
Promise链
通常我们实践Promise都是链式调用的,之所以能够发生链式调用是因为promise.then又返回了一个promise,新手常犯的一个错误就是将几个处理程序handler添加到一个Promise上,以为它们之间会相互传递数据,但其实它们各自独立运行:
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
示例代码中所有的promise.then都会返回相同的结果,实践中虽然很少这么使用,但也是个我之前不知道的点。
Promise错误处理
异步操作失败是在所难免的,如果出现错误,相应的promise就会reject,当失败时程序就交由最近的rejection处理函数:
fetch('www.test.com')
.then(response => response.json())
.catch(err => alert(err)) //解析json发生错误时,交由最近的catch处理
.catch(err2 => alert(err2));
相当于Promise在执行executor函数时候,函数体内有一个隐形的try...catch,当异常发生时即被捕获,所以下面代码处理结果相同。
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(alert); // Error: Whoops!
new Promise((resolve, reject) => {
reject(new Error("Whoops!"));
}).catch(alert); // Error: Whoops!
如果错误发生时候,代码没有调用catch捕获,那么javascript引擎就会跟踪此类rejection,抛出一个全局的错误,至少控制台会打印出错信息。
在浏览器中,可以使用unhandledrejection事件来捕获:
window.addEventListener('unhandledrejection', function(event) {
// the event object has two special properties:
alert(event.promise); // [object Promise] - 产生错误的 promise
alert(event.reason); // Error: Whoops! - 未处理的错误对象
});
new Promise(function() {
throw new Error("Whoops!");
}); // 没有 catch 处理错误
PromiseAPI
Promise类有5种常用静态方法:resolve、reject、all、allSettled、race
- resolve 给定值返回resolved promise
let promise = Promise.resolve(value);
//等价于
let promise = new Promise(resolve => resolve(value));
- reject 返回一个带有error的rejected promise
let promise = Promise.reject(error);
//等价于
let promise = new Promise((resolve, reject) => reject(error));
- all 需要并行执行promises的时候
let promise = Promise.all([...promises...]);
需要注意的是,返回结果和promises中的顺序是相对的,即使一个promise需要很长时间来resolve,它仍然是结果数组中的一个;并且如果其中一个promise的结果为rejected,则整个Promise.all返回的promise会立即处理这个rejected error,同时其他promise就被忽略,举个例子假如我们同时进行了多个fetch操作,其中一个失败了,其他的fetch操作仍然会执行,但是Promise.all的返回结果会忽略它们的结果;Promise.all不能取消,因为promise中没有cancellation的设计;
通常给all的传参都是数组,但是也可以传可迭代类型的集合,但是如果这些对象中任意一个不是promise,它将直接被包装进入Promise.resolve
//all的参数可以接受可迭代的promise集合
let promise = Promise.all(iterable)
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000)
}),
2, // 视为 Promise.resolve(2)
3 // 视为 Promise.resolve(3)
]).then(alert); // 1, 2, 3
- allSettled 新增的特性,旧浏览器需要polyfills支持,和all不同的是可以等待所有的promise被处理 ,也就是说即使一个被reject,仍然会等待其他的promise
//polyfill
if(!Promise.allSettled) {
Promise.allSettled = function(promises) {
return Promise.all(promises.map(p => Promise.resolve(p).then(v => ({
state: 'fulfilled',
value: v,
}), r => ({
state: 'rejected',
reason: r,
}))));
};
}
- race 赛马机制,哪个promise最先处理完,就返回哪个promise的处理结果,忽略其他
let promise = Promise.race(iterable);
如何实现一个Promise类?
promise的基本框架
class MyPromise {
constructor(executor) {
this.value = null
this.error = null
const resolve = (value) => {
this.value = value;
}
const reject = (error) => {
this.error = error;
}
// 外层控制异常捕获
try {
executor(resolve, reject);
} catch(error) {
reject(error)
}
}
}
export default MyPromise;
状态机的实现
const PENDING = "pending";
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
lass MyPromise {
constructor(executor) {
this.value = null;
this.error = null;
this.state = PENDING;
const resolve = (value) => {
if (this.state == PENDING) {
this.state = FULFILLED;
this.value = value;
}
}
const reject = (error) => {
if (this.state == PENDING) {
this.state = REJECTED;
this.error = error;
}
}
...
}
}
module.exports = MyPromise;
then方法的实现
const PENDING = "pending";
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
lass MyPromise {
constructor(executor) {
...
}
then(onFulfilled, onRejected) {
if (this.state == FULFILLED) {
onFulfilled(this.value);
} else if (this.state == REJECTED){
onRejected(this.error);
}
}
}
module.exports = MyPromise;
异步调用的实现
上面代码在同步执行下可以正确运行,但是在异步运行下就有问题了
//异步测试case
const Promise = require('./myPromise');
const promise = new Promise((resolve, reject) => {
// resolve(1)
setTimeout(() => {
console.log('gogogo');
resolve(2);
}, 1000)
});
//由于then方法先于setTimeout执行,而此时promise的state还是pending,
//因此then方法中的逻辑永远不会执行
promise.then((value) => {
console.log(value);
}, (error) => {
console.log(error);
});
解决方案:
class MyPromise {
constructor(executor) {
...同上
//创建回调数组
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state == PENDING) {
this.state = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach((onFulfillCallback) => {
//轮寻
onFulfillCallback(this.value);
})
}
}
const reject = (error) => {
if (this.state == PENDING) {
this.state = REJECTED;
this.error = error;
this.onRejectedCallbacks.forEach((onRejectedCallback) => {
//轮寻
onRejectedCallback(this.error);
})
}
}
...同上
}
then(onFulfilled, onRejected) {
//异步调用下,state仍是pending,因此走到最后else逻辑
if (this.state == FULFILLED) {
onFulfilled(this.value);
} else if (this.state == REJECTED){
onRejected(this.error);
} else {
//将回调函数保存在回调数组中
this.onFulfilledCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}
}
}
链式调用的实现
显然then方法中需要返回一个新的promise,并且要将上一个promise的结果带入
then(onFulfilled, onRejected) {
const nextPromise = new MyPromise((resolve, reject) => {
if (this.state == FULFILLED) {
onFulfilled(this.value);
} else if (this.state == REJECTED){
onRejected(this.error);
} else {
this.onFulfilledCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}
})
return nextPromise;
}
上面只是链式调用的基本框架,内部处理逻辑相似,只是返回一个新的Promise,但是没有将上一个处理的结果带入,并传递到下个resolve或reject
下面看下完整代码,当上次返回结果是一个新的Promise类型时,需要递归调用
resolvePromise(nextPromise, lastResult, resolve, reject) {
if (nextPromise == lastResult) {
reject(new TypeError('Chaining Cycle'));
}
if (lastResult && typeof lastResult === 'object' || typeof lastResult === 'function') {
let used;
try {
//如果还是一个promise
let then = lastResult.then;
if (typeof then === 'function') {
then.call(lastResult, (result) => {
if (used) return;
used = true;
this.resolvePromise(nextPromise, result, resolve, reject);
}, (err) => {
if (used) return;
used = true;
reject(err);
})
} else {
if (used) return;
used = true;
resolve(lastResult)
}
} catch(error) {
if (used) return;
used = true;
reject(error);
}
} else {
resolve(lastResult);
}
}
then(onFulfilled, onRejected) {
const nextPromise = new MyPromise((resolve, reject) => {
if (this.state == FULFILLED) {
try {
let lastResolved = onFulfilled(this.value)
this.resolvePromise(nextPromise, lastResolved, resolve, reject);
} catch (error) {
reject(error);
}
} else if (this.state == REJECTED){
try {
let lastRejected = onRejected(this.error);
this.resolvePromise(nextPromise, lastRejected, resolve, reject);
} catch(error) {
reject(error);
}
} else {
this.onFulfilledCallbacks.push(()=>{
try {
let lastResolved = onFulfilled(this.value)
this.resolvePromise(nextPromise, lastResolved, resolve, reject);
} catch (error) {
reject(error);
}
});
this.onRejectedCallbacks.push(() => {
try {
let lastRejected = onRejected(this.error);
this.resolvePromise(nextPromise, lastRejected, resolve, reject);
} catch(error) {
reject(error);
}
});
}
})
return nextPromise;
}
异常处理的实现
catch方法其实只是then方法的一次特殊调用
catch(onRejected){
return this.then(null, onRejected);
}