Promise解决了什么问题
在
Promise
出现之前,存在嵌套关系的多个异步操作往往是下面这样的:通过层层回调(回调地狱)来满足这种嵌套关系。真实环境的代码在每次异步操作前肯定还存在很多的逻辑处理,随着代码量的增加,可维护性会越来越差。
const fs = require('fs');
fs.readFile('./a.txt', 'utf8', (err, data) => {
// ...
fs.readFile(data, 'utf8', (err, data) => {
// ...
fs.readFile(data, 'utf8', (err, data) => {
console.log(data);
// ...
})
})
})
复制代码
逻辑关系:
有了Promise之后,我们可以把上面的代码改写成这样:
const readFIlePromise = filename => {
return new Promise((resolve, reject) => {
fs.readFile(filename, 'utf8', (err, data) => {
if (err) reject(err);
resolve(data);
})
})
}
readFIlePromise('./a.txt')
.then(data => {
// ...
return readFIlePromise(data);
})
.then(data => {
// ...
return readFIlePromise(data);
})
.then(data => {
console.log(data);
// ...
})
.catch(err => {
console.error(err);
})
复制代码
逻辑关系:
看,Promise
出现以后,用链式调用的写法改写嵌套写法是不是清晰了很多
如何实现一个Promise
很多实现
Promise
的库,比较著名的像bluebird
、ES6-Promise
等都是基于Promise A+
规范实现的。基于Promise A+
规范实现的最明显的好处就是:可以互相调用。所以学习Promise
,还是建议把A+
规范读一遍。
回顾一下Promise
我们先回顾一下
Promise
,先知道怎么用,才能知道怎么写对吧。看代码:
const p1 = new Promise((resolve, reject) => {
console.log('创建了p1');
resolve('p1');
});
const p2 = p1.then(data => {
console.log('p1-data', data);
throw new Error('错误了');
});
const p3 = p2.then(data => {
console.log('p2-data', data);
}).catch(err => {
console.log('p2-error', err);
})
// 创建了p1
// p1-data p1
// p2-error Error: 错误了
复制代码
单从上面的代码层面,我们可以发现Promise
具有下面几个特征:
- 实例化的过程需要接收一个函数(暂且叫
executor
),内部会立即执行executor
这个函数; executor
执行成功的话就执行resolve
,将成功的数据传递出去。失败/错误就会执行reject
,传递出错误信息;
结合Promise A+
规范,我们可以总结出Promise
所具有的特征:
Promise
只有三种状态:pending
、fulfilled
、rejected
;new Promise
的时候,需要传入一个执行器executor
,executor
会立即执行;executor
接收两个参数,分别是resolve
、reject
;- 默认状态是
pending
,pending
状态可以转为fulfilled
状态或者rejected
状态,状态一旦变更,就不可改变; fulfilled
状态必须有一个value
来保存成功的结果;rejected
状态必须有一个reason
来保存失败的结果;Promise
必须要有一个then
方法,then
方法接收两个参数,分别是成功后的回调onFulfilled
、失败后的回调onRejected
;Promise
成功后,将执行then
方法的成功回调onFulfilled
,并且value
将作为成功后回调的参数;Promise
失败后,将执行then
方法的失败回调onRejected
,并且reason
将作为失败后回调的参数;
tips:
光看上面的文字一脸懵逼,很正常,我当时也这样。可以直接看下面的实现,根据实现再去对照
Promise A+
规范更容易理解:
1. 先实现一个基础版的Promise
基于上面的特征,先慢慢来,我们先去实现一个基础版的
Promise
:
// 先定义三种状态:pending、fulfilled、rejected
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class Promise {
/**
* 执行器
* @param {Function} executor
*/
constructor(executor) {
this.status = PENDING; // 初始化是pending状态
this.value = undefined; // 保存成功的数据
this.reason = undefined; // 保存失败的原因
// 成功状态执行该函数
const resolve = data => {
if(this.status === PENDING) {
this.status = FULFILLED;
this.value = data;
}
}
// 失败状态执行该函数
const reject = reason => {
if(this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
}
}
try {
// new 的时候立即执行executor
executor(resolve, reject);
} catch(err) {
// 对于执行器执行过程中抛出的错误,我们也用reject抛出
reject(err);
}
}
then(onFulfilled, onRejected) {
if(this.status === FULFILLED) {
onFulfilled(this.value);
};
if(this.status === REJECTED) {
onRejected(this.reason);
}
}
}
复制代码
测试Demo 1
new Promise((resolve, reject) => {
resolve(1);
})
.then(data => {
console.log('success', data);
}, err => {
console.log('err', err);
})
// success 1
复制代码
基础版的Promise
已经完成,但是它只支持最基础的功能-传递普通值(成功/失败)。
测试Demo 2
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000)
})
.then(data => {
console.log('success', data);
}, err => {
console.log('err', err);
})
// 什么都没有打印
复制代码
Demo 2
之所以什么都没有打印,是因为new Promise
传入的executor
函数过了1
秒才变成成功状态,而then
方法在executor
的同步代码执行完了以后就会立即执行(此时Promise
状态还是pending
,所以then
的两个回调都不会执行)。
2. 升级一下基础版Promise
// 先定义三种状态:pending、fulfilled、rejected
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class Promise {
/**
* 执行器
* @param {Function} executor
*/
constructor(executor) {
this.status = PENDING; // 初始化是pending状态
this.value = undefined; // 保存成功的数据
this.reason = undefined; // 保存失败的原因
// +
this.onFulfilledCallbacks = [] // 保存成功状态的回调队列
this.onRejectedCallbacks = [] // 保存失败状态的回调队列
// 成功状态执行该函数
const resolve = data => {
if(this.status === PENDING) {
this.status = FULFILLED;
this.value = data;
// +
this.onFulfilledCallbacks.forEach(cb => cb());
}
}
// 失败状态执行该函数
const reject = reason => {
if(this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// +
this.onRejectedCallbacks.forEach(cb => cb());
}
}
try {
// new 的时候立即执行executor
executor(resolve, reject);
} catch(err) {
// 对于执行器执行过程中抛出的错误,我们也用reject抛出
reject(err);
}
}
then(onFulfilled, onRejected) {
if(this.status === FULFILLED) {
onFulfilled(this.value);
};
if(this.status === REJECTED) {
onRejected(this.reason);
}
// +
if(this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
onFulfilled(this.value);
});
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
})
}
}
}
复制代码
测试Demo 3
new Promise((resolve, reject) => {
setTimeout(() => {
reject(2);
}, 1000)
})
.then(data => {
console.log('success', data);
}, err => {
console.log('err', err);
})
// 1s以后打印出: err 2
复制代码
3. 给Promise
加上值穿透和链式调用
new Promise((resolve, reject) => {
resolve(3);
})
.then()
.then()
.then(data => {
console.log('success', data);
}, err => {
console.log('err', err);
})
复制代码
我们目前实现的
Promise
还不支持上面这种形式的调用(即值穿透)。并且还需要根据A +
规范2.2
、2.3
来完善我们的Promise
。所以建议对着A +
规范看下面的实现过程:
test.js
// 先定义三种状态:pending、fulfilled、rejected
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 2.3
const resolvePromise = (promise, x, resolve, reject) => {
// 2.3.1 如果promise和x是同一个,则reject,失败的原因是一个类型错误
if (promise === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
// 2.3.3.3.3 避免多次调用,状态变更后(resolve/reject),后面就不再执行
let called = false;
// 2.3.3 如果x是一个对象/函数
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
// 2.3.3.1 定义一个then变量保存x.then
const then = x.then;
// 2.3.3.3 如果then是一个函数
if (typeof then === 'function') {
// 2.3.3.3 则调用then方法,x作为this,第一个参数是成功状态的回调,第二个参数是失败状态的回调
then.call(x, y => {
if(called) return;
called = true;
// 2.3.3.3.1 执行成功回调的话,则继续执行resolvePromise
resolvePromise(promise, y, resolve, reject);
}, r => {
if(called) return;
called = true;
// 2.3.3.3.2 执行失败状态的回调的话,则执行reject
reject(r);
});
} else {
// 2.3.3.4 如果then不是一个函数,则resolve,参数是x
resolve(x);
}
} catch(e) {
if(called) return;
called = true;
// 2.3.3.2 如果获取x.then属性的时候报错,则reject,错误原因是catch到的错误
reject(e);
}
} else {
// 2.3.4 如果x不是一个对象/函数,则resolve,参数是x
resolve(x);
}
}
class Promise {
/**
* 执行器
* @param {Function} executor
*/
constructor(executor) {
this.status = PENDING; // 初始化是pending状态
this.value = undefined; // 保存成功的数据
this.reason = undefined; // 保存失败的原因
this.onFulfilledCallbacks = [] // 保存成功状态的回调队列
this.onRejectedCallbacks = [] // 保存失败状态的回调队列
// 成功状态执行该函数
const resolve = data => {
if(this.status === PENDING) {
this.status = FULFILLED;
this.value = data;
this.onFulfilledCallbacks.forEach(cb => cb());
}
}
// 失败状态执行该函数
const reject = reason => {
if(this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(cb => cb());
}
}
try {
// new 的时候立即执行executor
executor(resolve, reject);
} catch (err) {
// 对于执行器执行过程中抛出的错误,我们也用reject抛出
reject(err);
}
}
// 2.2
then (onFulfilled, onRejected) {
// 值穿透
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
let promise = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
// 2.2.4 onFulfilled/onRejected放到执行上下文堆栈的最后执行
setTimeout(() => {
try {
const x = onFulfilled(this.value);
// 2.2.7.1 onFulfilled/onRejected有返回值的话,就调用resolvePromise
resolvePromise(promise, x, resolve, reject);
} catch (e) {
// 2.2.7.2 onFulfilled/onRejected抛出错误的话,就执行reject,错误原因是就是catch捕获的错误
reject(e);
}
}, 0);
};
if (this.status === REJECTED) {
// 2.2.4 onFulfilled/onRejected放到执行上下文堆栈的最后执行
setTimeout(() => {
try {
const x = onRejected(this.reason);
// 2.2.7.1 onFulfilled/onRejected有返回值的话,就调用resolvePromise
resolvePromise(promise, x, resolve, reject);
} catch (e) {
// 2.2.7.2 onFulfilled/onRejected抛出错误的话,就执行reject,错误原因是就是catch捕获的错误
reject(e)
}
}, 0);
}
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
// 2.2.4 onFulfilled/onRejected放到执行上下文堆栈的最后执行
setTimeout(() => {
try {
const x = onFulfilled(this.value);
// 2.2.7.1 onFulfilled/onRejected有返回值的话,就调用resolvePromise
resolvePromise(promise, x, resolve, reject);
} catch (e) {
// 2.2.7.2 onFulfilled/onRejected抛出错误的话,就执行reject,错误原因是就是catch捕获的错误
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
// 2.2.4 onFulfilled/onRejected放到执行上下文堆栈的最后执行
setTimeout(() => {
try {
const x = onRejected(this.reason);
// 2.2.7.1 onFulfilled/onRejected有返回值的话,就调用resolvePromise
resolvePromise(promise, x, resolve, reject);
} catch (e) {
// 2.2.7.2 onFulfilled/onRejected抛出错误的话,就执行reject,错误原因是就是catch捕获的错误
reject(e)
}
}, 0);
})
}
});
// 2.2.7 then方法必须返回一个promise
return promise;
}
}
复制代码
写完需要使用Promise A+
提供的测试脚本验证我们的Promise
,使用方法:
- 安装测试脚本:
npm install -g promises-aplus-tests
; - 测试文件末尾加入如下代码,并导出:
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve,reject)=>{
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;
}
module.exports = Promise;
复制代码
- 最后,执行测试命令:
promises-aplus-tests test.js
; - 测试结果:
实现Promise.prototype.catch()
catch
方法实际上就是借助then
方法实现的,只不过成功的回调传的null
。
Promise.prototype.catch = function (errCallBack){
return this.then(null, errCallBack)
}
// test
new Promise((resolve, reject) => {
reject(2);
})
.then(data => {
console.log('success', data);
})
.catch(err => {
console.log('catch', err); // catch 2
})
复制代码
实现Promise.resolve()
Promise.resolve
是产生一个成功的promise
。
Promise.resolve = function(data) {
return new Promise((resolve, reject) => {
resolve(data);
})
}
// test
Promise.resolve(new Promise((resolve, reject) => {
resolve('ok');
})).then(data=>{
console.log(data,'success') // ok success
}).catch(err=>{
console.log(err,'error')
})
复制代码
Promise.resolve
还需要等待功能。即如果data
是一个promise
,需要data
执行完毕。改写constructor
的resolve
方法:
// 成功状态执行该函数
const resolve = data => {
// 新增:如果data是Promise,那进行递归解析它
if (data instanceof Promise) {
return data.then(resolve, reject);
}
this.status = FULFILLED;
this.value = data;
this.onFulfilledCallbacks.forEach(cb => cb());
}
// test
Promise.resolve(new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok');
}, 3000);
})).then(data=>{
console.log(data,'success') // 3秒后打印:ok success
}).catch(err=>{
console.log(err,'error')
})
复制代码
这样,Promise.resolve
就具备了等待功能.
实现Promise.reject()
该方法是返回一个失败的
promise
,直接将错误的结果当成reason
抛出即可。
Promise.reject = function(reason) {
return new Promise((resolve, reject) => {
reject(reason);
})
}
// test
Promise.reject(2).then(data=>{
console.log(data,'success')
}).catch(err=>{
console.log(err,'error') // 2 error
})
复制代码
实现Promise.prototype.finally()
该方法表示不管成功/失败都会执行(并不是最后执行)。如果上一次返回的成功的结果,
finally
执行完内部的代码以后就会把上一次成功的结果返回。如果上个promise
是失败,则会把失败的原因抛出。
Promise.prototype.finally = function(callBack) {
return this.then(data => {
return Promise.resolve(callBack()).then(() => data);
}, reason => {
return Promise.resolve(callBack()).then(() => {
throw reason;
})
})
}
// test
Promise.resolve(1).finally(()=>{
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(2)
}, 2000);
})
}).then(data=>{
console.log(data,'success') // 2秒后打印:1 success
}).catch(err=>{
console.log(err,'error')
})
复制代码
实现Promise.race()
该方法用来处理多个请求,哪个请求先执行完就返回它的结果。
Promise.race = function(promiseList) {
if (!Array.isArray(promiseList)) {
throw new TypeError('You must pass array')
}
return new Promise((resolve, reject) => {
for (let i = 0, len = promiseList.length; i < len; i++) {
const val = promiseList[i];
if (val && typeof val.then === 'function') {
val.then(resolve, reject);
} else { // 普通值
resolve(val);
}
}
})
}
// test
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok1');
}, 3000);
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('ok2');
}, 2000);
})
Promise.race([1, 2, 3, p1, p2]).then(data => {
console.log('success', data);
}, err => {
console.log('error', err);
})
// success 1
复制代码
实现Pomise.all()
该方法用于处理多个请求,如果多个请求都成功,则返回一个数组,里面每一项是各个请求成功的结果。只要有一个失败,则失败。
Promise.all = funcion(promiseList) {
if (!Array.isArray(promiseList)) {
throw new TypeError('You must pass array')
}
return new Promise((resolve, reject) => {
const resultArr = [];
const len = promiseList.length;
let currentIndex = 0;
const getResult = (key, val) => {
resultArr[key] = val;
if (++currentIndex === len) {
resolve(resultArr);
}
}
for (let i = 0; i < len; i++) {
const val = promiseList[i];
if (val && typeof val.then === 'function') {
val.then(data => {
getResult(i, data);
}, reject);
} else { // 普通值
getResult(i, val);
}
}
})
}
// test
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok1');
}, 1000);
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok2');
}, 2000);
})
Promise.all([1,2,3,p1,p2]).then(data => {
console.log('success', data);
}, err => {
console.log('error', err);
})
// 2秒后打印:success [ 1, 2, 3, 'ok1', 'ok2' ]
复制代码
实现Promise.allSettled()
该方法会等到所有参数实例都返回结果,不管是
reject
还是resolve
。最后会返回一个数组,每一项都包含状态和值两个属性。
Promise.allSettled = function(promiseList) {
if (!Array.isArray(promiseList)) {
throw new TypeError('You must pass array')
}
return new Promise((resolve, reject) => {
const resultArr = [];
const len = promiseList.length;
let currentIndex = 0;
const getResult = (key, val, status) => {
resultArr[key] = {
status: status,
};
resultArr[key].status === 'fulfilled' ? resultArr[key].value = val : resultArr[key].reason = val;
if (++currentIndex === len) {
resolve(resultArr);
}
}
for(let i = 0; i < len; i++) {
const val = promiseList[i];
if (val && typeof val.then === 'function') {
val.then(data => {
getResult(i, data, 'fulfilled');
}, reason => {
getResult(i, reason, 'rejected');
})
} else {
getResult(i, val, 'fulfilled');
}
}
})
}
// test
const promise1 = Promise.resolve('ok1');
const promise2 = Promise.reject('err1');
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok2');
}, 1000);
})
const allSettledPromise = Promise.allSettled([promise1, promise2, promise3]);
allSettledPromise.then(function (results) {
console.log(results);
});
// 1秒后输出
/*
[
{ status: 'fulfilled', value: 'ok1' },
{ status: 'rejected', reason: 'err1' },
{ status: 'fulfilled', value: 'ok2' }
]
*/
复制代码
实现Promise.any()
该方法和
Promise.all
的情况相反,只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。
Promise.any = function(promiseList) {
if (!Array.isArray(promiseList)) {
throw new TypeError('You must pass array')
}
return new Promise((resolve, reject) => {
const resultArr = [];
const len = promiseList.length;
let currentIndex = 0;
const getResult = (key, val) => {
resultArr[key] = val;
if (++currentIndex === len) {
reject(resultArr);
}
}
for (let i = 0; i < len; i++) {
const val = promiseList[i];
if (val && typeof val.then === 'function') {
val.then(resolve, reason => {
getResult(i, reason);
});
} else { // 普通值
resolve(val);
}
}
})
}
// test
const promise1 = Promise.reject('err1');
const promise2 = Promise.reject('err2');
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('err3');
}, 1000);
})
const allSettledPromise = Promise.any([promise1, promise2, promise3]);
allSettledPromise
.then(data => {
console.log('resolve', data);
})
.catch(reason => {
console.log('reject', reason);
})
// 1秒后输出:reject [ 'err1', 'err2', 'err3' ]
复制代码
至此,ES Promise
的方法都实现完了。自己实现完一遍以后,对Promise
、then
的链式调用以及值穿透的理解会更加透彻。