实现符合A+规范的Promise
本文最后更新于:2022年5月31日 早上
promise
Promise 对象用于处理异步操作,它表示一个尚未完成且预计在未来完成的异步操作。
特性
- 解决回调地狱,使用统一的 API 接口处理异步任务
- 链式调用
- 返回值穿透(onFulfilled 不是 function 时,上一步的执行结果 value 将继续向后传递)
- 错误冒泡(
回调地狱
function f1(){
//f2的执行需要等待f1的结果
return f2(){
f3(){
f4(){
f5(){
...
}
...
}
}
}
}
Promise 对象有以下两个特点
对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:
pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。如果改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
方法
Promise.prototype.then()
- 为 Promise 实例添加状态改变时的回调函数
- then 方法的第一个参数是
resolved
状态的回调函数,第二个参数(可选)是rejected
状态的回调函数 - then 方法返回的是一个新的 Promise 实例,可以 then 方法后面再调用另一个 then 方法
getJSON("/post/1.json")
.then(function (post) {
return getJSON(post.commentURL);
})
.then(
function funcA(comments) {
console.log("resolved: ", comments);
},
function funcB(err) {
console.log("rejected: ", err);
}
);
// 第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,就调用funcA,如果状态变为rejected,就调用funcB。
Promise.prototype.catch()
指定发生错误时的回调函数
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个
catch
语句捕获
// 一般来说,不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。
// bad
promise.then(
function (data) {
// success
},
function (err) {
// error
}
);
// good
promise
.then(function (data) {
//cb
// success
})
.catch(function (err) {
// error
});
跟传统的
try/catch
代码块不同的是,如果没有使用catch
方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应
- catch 方法返回的还是一个 Promise 对象,因此后面还可以接着调用 then 方法
Promise.prototype.finally()
finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
finally 方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是 fulfilled 还是 rejected。这表明,finally 方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
Promise.all()
- Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
const p = Promise.all([p1, p2, p3]);
- p 的状态由 p1、p2、p3 决定,分成两种情况。
只有 p1、p2、p3 的状态都变成
fulfilled
,p 的状态才会变成fulfilled
,此时 p1、p2、p3 的返回值组成一个数组,传递给 p 的回调函数。只要 p1、p2、p3 之中有一个被
rejected
,p 的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给 p 的回调函数
如果作为参数的 Promise 实例,自己定义了 catch 方法,那么它一旦被 rejected,并不会触发 Promise.all()的 catch 方法
Promise.race()
Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。
//example
const p = Promise.race([
fetch("/resource-that-may-take-a-while"),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error("request timeout")), 5000);
}), // 5s内请求不resolve则抛出错误
]);
p.then(console.log).catch(console.error);
promise.try()
不想区分,函数 f 是同步函数还是异步操作,但是想用 Promise 来处理它
try {
database.users.get({id: userId})
.then(...)
.catch(...) // promise错误
} catch (e) { // 同步错误
// ...
}
// try catch捕获同步错误
// 用promise.try改写
Promise.try(() => database.users.get({id: userId}))
.then(...)
.catch(...)
tips
- 立即
resolve()
的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行
Promise.resolve()方法允许调用时不带参数,直接返回一个 resolved 状态的 Promise 对象。
setTimeout(function () {
console.log("three");
}, 0);
Promise.resolve().then(function () {
console.log("two");
});
console.log("one");
// one
// two
// three
Promise.reject()
方法的参数,会原封不动地作为 reject 的理由,变成后续方法的参数const thenable = { then(resolve, reject) { reject("出错了"); }, }; Promise.reject(thenable).catch((e) => { console.log(e); }); // catch方法的参数不是reject抛出的“出错了”这个字符串,而是thenable对象。
手写 Promise
- 测试 a+规范
promises-aplus-tests
node promises-aplus-tests yourpeomise.js
// yourpromise.js
MyPromise.defer = MyPromise.deferred = function () {
let dfd = {};
dfd.promise = new MyPromise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
};
module.exports = MyPromise
- resolvePromise
const isComplex = (obj) => obj !== null && (typeof obj === "function" || typeof obj === "object");
// 判断then之后的状态
function resolvePromise(x, promise2, resolve, reject) {
if (x === promise2) {
reject(new TypeError("Chaining cycle detected for promise"));
}
if (isComplex(x)) {
let hasRes = false;
try {
const then = x.then;
// 如果x是promise,继续判断
if (typeof then === "function") {
then.call(
x,
(v) => {
if (hasRes) return;
hasRes = true;
resolvePromise(v, promise2, resolve, reject);
},
(r) => {
if (hasRes) return;
hasRes = true;
reject(r);
}
);
} else {
// 如果x是个对象
resolve(x);
}
} catch (e) {
if (hasRes) return;
hasRes = true;
reject(e);
}
} else {
resolve(x);
}
}
// 通用常量
const PENDING = Symbol('PENDING');
const FULFILLED = Symbol('FULFILLED');
const REJECTED = Symbol('REJECTED');
function 版
sfunction MyPromise(executor) { this.status = PENDING; this.data = undefined; this.onFulfilledCb = []; this.onRejectedCd = []; const resolve = (value) => { setTimeout(() => { // 放在下个宏任务等promise2都执行得出状态 if (this.status === PENDING) { this.status = FULFILLED; this.data = value; this.onFulfilledCb.forEach((cb) => cb()); } }); }; const reject = (reason) => { setTimeout(() => { // 放在下个宏任务等promise2都执行得出状态 if (this.status === PENDING) { this.status = REJECTED; this.data = reason; this.onRejectedCd.forEach((cb) => cb()); } }); }; try { executor(resolve, reject); } catch (e) { reject(e); } } MyPromise.prototype.then = function (onFulfill, onReject) { onFulfill = typeof onFulfill === "function" ? onFulfill : (value) => value; onReject = typeof onReject === "function" ? onReject : (err) => { throw err; }; let promise2 = new Promise((resolve, reject) => { if (this.status === PENDING) { // push进去的时候promise2还没有执行完 this.onFulfilledCb.push(() => { try { let x = onFulfill(this.data); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }); this.onRejectedCd.push(() => { try { let x = onReject(this.data); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }); // 由于new Promise构造函数逻辑 // 可以简写 } else if (this.status === FULFILLED) { // 如果返回一个promise,依然需要判断他后续的状态,交给下一个then时去判断👆的代码 setTimeout(() => onFulfill(this.data)); // setTimeout(()=>{ // try{ // let x = onFulfill(this.data) // resolvePromise(x,promise2,resolve,reject) // }catch(e){ // reject(e) // } // }) } else if (this.status === REJECTED) { setTimeout(() => onReject(this.data)); } }); return promise2; };
class 版
class MyPromise {
constructor(executor) {
try {
executor(this.resolve, this.reject);
} catch (e) {
this, reject(e);
}
}
status = PENDING;
data = undefined;
successCallback = [];
failCallback = [];
resolve = (value) => {
if (value instanceof MyPromise) {
return value.then(this.resolve, this.reject);
}
setTimeout(() => {
if (this.status === PENDING) {
this.data = value;
this.status = FULFILLED;
this.successCallback.forEach((cb) => cb());
}
});
};
reject = (reason) => {
setTimeout(() => {
if (this.status === PENDING) {
this.data = reason;
this.status = REJECTED;
this.failCallback.forEach((cb) => cb());
}
});
};
then(onResolve, onReject) {
onResolve = typeof onResolve === "function" ? onResolve : (val) => val;
onReject =
typeof onReject === "function"
? onReject
: (err) => {
throw err;
};
const promise2 = new MyPromise((resolve,reject)=>{
if(this.status === PENDING){
this.successCallback.push(()=>{
try{
const x = onResolve(this.data)
resolvePromise(x,promise2,resolve,reject)
}catch(e){
reject(e)
}
})
this.failCallback.push(()=>{
try{
const x = onReject(this.data)
resolvePromise(x,promise2,resolve,reject)
}catch(e){
reject(e)
}
})
}else if(this.status === FULFILLED){
setTimeout(()=>onResolve(this.data))
}else if(this.status === REJECTED){
setTimeout(()=>onReject(this.data))
}
})
return promise2
}
}
- API
Promise.resolve = (value) => new Promise((resolve, reject) => resolve(value));
Promise.reject = (reason) => new Promise((resolve, reject) => reject(reason));
// catch
Promise.prototype.catch = (onReject) => {
return this.then(null, onReject);
};
// finally
Promise.prototype.finally = function (callback) {
return this.then(
(value) => {
Promise.resolve(callback()).then(() => value);
},
(reason) => {
Promise.resolve(callback()).then(() => {
throw reason;
});
}
);
};
// race
Promise.race = function (pArr) {
// 抛出第一个改变的状态
return new Promise((resolve, reject) => {
pArr.forEach((pItem) => pItem.then(resolve, reject));
});
};
// any
Promise.any = function (pArr) {
// 有resolve就resolve
// 都没有resolve则返回一个由所有err组成的AggregateError对象
return new Promise((resolve, reject) => {
if (!pArr.length) return reject(new AggregateError([]));
const res = [];
pArr.forEach((pItem, index) => {
pItem.then(
(v) => {
resolve(v);
},
(e) => {
res[index] = e;
}
);
});
reject(new AggregateError(res));
});
};
// all
Promise.all = function (pArr) {
// resolve全部value
// 有err就reject
return new Promise((resolve, reject) => {
if (!pArr.length) return resolve([]);
const res = [];
pArr.forEach((pItem, index) => {
pItem.then(
(v) => {
res[index] = v;
},
(e) => {
reject(e);
}
);
});
resolve(res);
});
};
// allSettled
Promise.allSettled = function (pArr) {
// 记录每个promise的状态
return new Promise((resolve, reject) => {
if (!pArr.length) return resolve([]);
const res = [];
pArr.forEach((pItem, index) => {
// if (pItem instanceof Promise) {
pItem.then(
(v) => {
res[index] = { status: "resolve", value: v };
},
(e) => {
res[index] = { status: "reject", reason: e };
}
);
// } else {
// res[index] = { status: "resolve", value: pItem };
// }
});
resolve(res);
});
};
同步函数 promise 化
const promisify =
(func) =>
(...args) =>
new Promise((resolve, reject) => {
args.push(function (err, value) {
if (err) reject(err);
else resolve(value);
});
func.apply(null, args);
});
面试题
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
});
//p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error("fail")), 3000);
});
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000);
});
p2.then((result) => console.log(result)).catch((error) => console.log(error));
// Error: fail
// 上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。