状态
要搞清Promise ,首先要知道Promise的三个状态
- pending (等待态)
- resolved 或 fulfilled (成功态)
- rejected (失败态)
其中,pending 可以转换到 resolved 或者 rejected,但是,resolved 不能转化为 rejected,rejected 也不能转换为 resolved。也就是说一个状态,一旦成功就不会再失败,一旦失败就不会再成功。新Promise实例默认状态为 pending。
then
每个Promise 实例都有then 方法,then 方法有另两个参数,分别是成功的回调和失败的回调。同一个实例可以有多个then 方法,当成功时,会调用所有then的成功方法,失败时,会调用所有then的失败方法。
另外,then方法是异步的,它还有一个逼格更高的名字叫微任务,与宏任务相对应,是跟事件环有关的,这里不做详细描述。
简单实现
明白了以上两个概念,基本上就可以去实现一个简陋版的Promise,代码如下
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
this.status = 'pending';
let resolve = value => {
this.value = value;
this.status = 'resolved';
}
let reject = reason => {
this.reason = reason;
this.status = 'rejected';
}
executor(resolve, reject);
}
then(onFulfilled, onRejected) {
if (this.status === 'resolved') {
onFulfilled(this.value);
}
if (this.status === 'rejected') {
onRejected(this.reason);
}
}
}
复制代码
error
当同步执行到executor时,其内部有可能会报错,而当内部报错的时候,Promise的状态就为rejected失败态,执行传入的reject方法。这里可以使用try catch捕获,则代码为:
try{
executor(resolve, reject);
}catch(e){
reject(e);
}
复制代码
异步状态修改
在executor中修改Promise状态,往往会遇到异步的方式,比如
let promise = new Promise((resolved, rejected) => {
setTimeout(()=>{
resolved('ok');
},1000);
})
promise.then((data)=>{
console.log(data);
},(err)=>{
console.log(err);
});
复制代码
当遇到此类情况时,如果是我们刚刚的方案,then会在resolve方法之前执行,所以当1秒后成功,并不会触发then 中的成功回调,那应该怎样处理这种情况呢?
事实上,这里可以通过事件的发布订阅去处理,当执行到then的时候,先去判断当前Promise的状态,如果是pending,那么就将成功和失败的回调存起来,当异步的resolve或者reject触发过后,再将存储好的成功或失败方法依次拿出来执行,有了这个思路过后,可以声明两个数组来进行存储:
constructor(executor) {
this.value = undefined;
this.reason = undefined;
this.status = 'pending';
this.onResolvedCallbacks = []; // 存放成功回调
this.onRejectedCallbacks = []; // 存放失败回调
...
}
复制代码
在then 中添加状态为pending的判断,当状态为pending是,将传入的成功和失败回调存入声明好的数组中:
then(onFulfilled, onRejected) {
if (this.status === 'resolved') {
onFulfilled(this.value);
}
if (this.status === 'rejected') {
onRejected(this.reason);
}
if (this.status === 'pending') {
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value);
});
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
});
}
}
复制代码
而在resolve和reject中,需要添加对回调函数数组的依次执行:
let resolve = value => {
if (this.status === 'pending') {
this.value = value;
this.status = 'resolved';
this.onResolvedCallbacks.forEach(fn => fn());
}
}
let reject = reason => {
if (this.status === 'pending') {
this.reason = reason;
this.status = 'rejected';
this.onRejectedCallbacks.forEach(fn => fn());
}
}
复制代码
链式调用
使用
Promise用来处理回调地狱,使用的就是then的链式调用,使代码看上去更容易维护。首先来看下链式调用的使用方式
在node中通常会使用fs 模块来执行文件读取,fs 提供了同步读取和异步读取,以异步为例,可以将这个链式读取Promise化:
const fs = require('fs');
function readFlie(url, encoding) {
return new Promise((resolved, rejected) => {
fs.readFile(url, encoding, (err, data) => {
if (err) {
rejected(err);
} else {
resolved(data);
}
});
})
}
readFlie('./a.txt', 'utf8').then((data) => {
return readFlie(data, 'utf8')
}).then((data) => {
return data
}).then((data) => {
console.log(data);
});
复制代码
值得注意的是,then中如果返回的是一个promise,那么下一个then 为这个promise的执行结果,如果then中返回的一个普通值,那么该值将会当做参数传入下一个then 的成功回调函数,如果then中抛出一个错误,那么接下来的then必要写错误回调去捕获这个抛出的错误,也可以使用catch去统一处理,所有的错误只能被捕获一次。另外,如果then中成功和失败的回调都没写的话,那么值会穿透该then 到达下一个then。
实现
要实现链式调用,主要是看当一个then执行过后,返回的数据结果是不是一个新的promise,根据promise A+ 规范,需要在then 中加一个promise2 予以返回,并且需要判断then中回调执行结果与这个promise2的关系,如下:
then(onFulfilled, onRejected) {
let promise2 = new Promise((resolve, reject) => {
if (this.status === 'resolved') {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
}
if (this.status === 'rejected') {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
}
if (this.status === 'pending') {
this.onResolvedCallbacks.push(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
});
this.onRejectedCallbacks.push(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
});
}
});
return promise2;
}
复制代码
其中的resolvePromise就是判断promise2 与 then中回调执行返回 x关系的函数,如下:
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError('x can not be promise2'))
}
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
resolvePromise(promise2, y, resolve, reject) // 递归直到解析为普通值为止
}, err => {
reject(err)
});
} else {
resolve(x)
}
} catch (e) {
reject(e);
}
} else {
resolve(x);
}
}
复制代码
其实写到这里,基本上已经实现了一个符合 promise A+ 规范的方案,另外再补充几个Promise的方法
catch
捕获then链上未被捕获的错误,通常catch 放在then 链的最后,也可以放中间,但是不建议这样做,程序会抛出警告,实现如下:
catch(onRejected) {
return this.then(null, onRejected);
}
复制代码
all
传入一堆promise,然后返回一个新的promise,当这一堆promise 都成功了,执行新promise的then方法的成功回调,返回值是一个数组,反之如果有一个失败就失败了,执行失败回调:
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let arr = [];
let i = 0;
function processData(index, data) {
arr[index] = data;
if (++i == promises.length) {
resolve(arr);
}
}
for (let i = 0; i < promises.length; i++) {
promises[i].then(data => { // data是成功的结果
processData(i, data);
}, reject);
}
})
}
复制代码
race
传入一堆promise,然后返回一个新的promise,谁先执行完,就以谁为新promise的结果
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(resolve, reject);
}
})
}
复制代码
[github]:github.com/kingDuiDui/…
[参考文档]: Promise A+ 规范
[原文地址]:kkking.site/Blog/javasc…