Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大
Promise在我们的开发中运用很广,在面试中也是衡量前端开发水平的重要指标,这篇文章将带领各位同学手把手写一个满足Promise/A+规范的Promise.
Let's go!
实现promise的基本框架
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。
- Promise构造函数接受一个函数作为参数。
- 该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
- 传入执行函数立即执行。
function MyPromise(executor) {
function resolve(result) {
console.log('resolved', result);
}
function reject(reason) {
console.log('rejected', reason);
}
try {
executor(resolve, reject);
} catch(error) {
reject(error.message);
}
}
增加状态机
Promise共有3种状态:pending(等待处理), fulfilled(成功), rejected(失败)
- Promise的状态是不可逆的:默认状态为pending, 状态只能由pending变成fulfilled, 或者pending变成rejected。
- resolve函数: 将Promise对象的状态从 pending变为fulfilled,并将异步操作的结果,作为参数传递出去。
- reject函数: 将Promise对象的状态从 pending变为rejected,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function MyPromise(executor) {
let self = this;
// 给promise构造函数增加状态,默认状态为pending
self.state = PENDING;
function resolve(result) {
// resolve函数将Promise状态从pending变为fulfilled
if (self.state === PENDING) {
self.state = FULFILLED;
console.log('resolved', result);
}
}
function reject(reason) {
// reject函数将Promise状态从pending变为rejected
if (self.state === PENDING) {
self.state = REJECTED;
console.log('rejected', reason);
}
}
try {
executor(resolve, reject);
} catch(error) {
reject(error.message);
}
}
实现then方法
Promise实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
- then方法可以接受两个回调函数作为参数。
- 第一个回调函数是Promise对象的状态变为fulfilled时调用
- 第二个回调函数是Promise对象的状态变为rejected时调用。
- 这两个函数都是可选的,不一定要提供。它们都接受Promise对象传出的值作为参数。
- then传入的回调函数必须等待promise状态变成fulfilled或者rejected之后才能调用。
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// 这两个函数都是可选的,不一定要提供。它们都接受Promise对象传出的值作为参数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => { return this.value };
onRejected = typeof onRejected === 'function' ? onRejected : () => { return this.reason };
// then传入的回调函数必须等待promise状态变成fulfilled或者rejected之后才能调用
if (this.state === PENDING) {
this.fulfilledCallback = onFulfilled;
this.rejectedCallback = onRejected;
}
if (this.state === FULFILLED) {
onFulfilled();
}
if (this.state === REJECTED) {
onRejected();
}
};
实现链式调用
Promise支持链式写法,即then方法后面再调用另一个then方法。那么会等待当前Promise执行完成之后再返回给下一次的then,Promise如果成功,就会走下一次then的成功回调,如果失败就会走下一次then的失败回调。
- then方法返回一个新的Promise实例。
- 后一个回调函数,需要等待前一个Promise对象的状态发生变化,才可被调用。
- then方法中返回的回调函数不能是自己本身,如果真的这样写,那么函数执行到里面时会等待promise的结果,这样一层层的状态等待就会形成回调地狱。
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function MyPromise(executor) {
const self = this;
// 给promise构造函数增加状态,默认状态为pending
self.state = PENDING;
self.value = undefined;
self.reason = undefined;
self.onResolvedCallbacks = [];
self.onRejectedCallbacks = [];
function resolve(result) {
// resolve函数将Promise状态从pending变为fulfilled
if (self.state === PENDING) {
self.state = FULFILLED;
self.value = result;
// 接受Promise对象传出的值作为参数。
self.onResolvedCallbacks.forEach(callback => {
callback();
});
}
}
function reject(reason) {
// reject函数将Promise状态从pending变为rejected
if (self.state === PENDING) {
self.state = REJECTED;
self.reason = reason;
// 接受Promise对象传出的值作为参数
self.onRejectedCallbacks.forEach(callback => {
callback();
});
}
}
try {
executor(resolve, reject);
} catch(error) {
reject(error.message);
}
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
let self = this;
// 这两个函数都是可选的,不一定要提供。它们都接受Promise对象传出的值作为参数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => { return this.value };
onRejected = typeof onRejected === 'function' ? onRejected : () => { return this.reason };
// 返回一个新的Promise实例
return new MyPromise((resolve, reject) => {
/**
* then传入的回调函数必须等待promise状态变成fulfilled或者rejected之后才能调用
* 如果Promise处理的为一个异步函数,那么当执行then的时候,Promise的状态仍然为pending,
* 此时我们并不知道要去执行onFulfilled回调还是onRejected回调,
* 所以先把这两个回调函数保存起来,等到promise状态改变之后,再根据最终promise的状态选择调用。
*/
if (self.state === PENDING) {
self.onResolvedCallbacks.push(function() {
let result = onFulfilled(self.value);
resolve(result);
});
self.onRejectedCallbacks.push(function() {
let result = onRejected(self.value);
reject(result);
});
}
if (self.state === FULFILLED) {
const result = onFulfilled(self.value);
resolve(result);
}
if (self.state === REJECTED) {
const reason = onRejected(self.reason);
reject(reason);
}
});
};
Promise 解决程序,完善promise.then()
在上述的例子中,我们假设传入的then回调onFulfilled 和 onRejected返回的都是普通值,但其实这2个回调函数的返回值可以是各种各样的,比如onFulfilled返回一个promise, 或者返回一个thenable 对象或者方法所有我们需要完善一下,使我们的代码能够兼容各种返回值.
创建resolvePromise方法,用来处理onFulfilled 和 onRejected的各种返回值
// onFulfilled 和 onRejected的返回值是开发者传入的,可能有各种问题。返回值也有很多种情况,
// 所以我们需要一个函数处理onFulfilled 和 onRejected 的各种返回值
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('不能循环调用'));
}
// 针对对象或者方法中有 then 方法,也就是所谓的 thenable 对象
if (isThenable(x)) {
let called;
try {
// 取then的时候可能会出错
let then = x.then;
if (typeof then === 'function') {
then.call(x, result => {
if (called) {
return;
}
called = true;
// 取得 x.then()的返回值之后,仍然需要对返回值进行判断,所以这里需要再次调用resolvePromise.
resolvePromise(x, result, resolve, reject);
}, error => {
if (called) {
return;
}
called = true;
reject(error);
});
} else {
resolve(x);
}
} catch (e) {
if (called) {
return;
}
called = true;
reject(e);
};
} else {
resolve(x);
}
};
创建isThenable,检测对象或者函数是否拥有then方法
// 判断对象或者函数是否有 then 方法
function isThenable(t) {
// if (t !== null && typeof t === 'object' || typeof t === 'function' && 'then' in t) {
if (t !== null && (typeof t === 'object' || typeof t === 'function')) {
return true;
}
return false;
}
Promise.all()
关键点:
- Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
- 直接在构造函数上增加all方法
- Promise.all()方法接受一个数组作为参数(可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例)
- 需要判断数组中每一个参数是否是Promise,是的话就执行该Promise的then方法, 将返回值放到并放到数组result中,如果是个普通值,直接讲该值放到result中
- 等待参数数组中所有的promise都执行完毕后再返回结果
MyPromise.all = function(arr) {
return new MyPromise((resolve, reject) => {
if (!Array.isArray(arr)) {
throw new TypeError('Promise.all 参数必须是数组');
}
// 定义存放最终结果的数组result
let result = [];
let count = 0;
addResult = (key, value) => {
count++;
result[key] = value;
// 当count 等于 传入数组的长度时,说明所有的promise执行完毕
if (count === arr.length) {
resolve(v);
}
}
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
// 判断item是否是thenable对象, 并且item.then是function
if (isThenable(item)) {
try {
let then = item.then;
if (typeof then === 'function') {
then(v => {
addResult(i, v);
}, e => reject(e));
} else {
addResult(i, item);
}
} catch (e) {
reject(e);
}
} else {
// item是普通值,直接把值加到result中
addResult(i, item);
}
}
});
}
Promise.race()
关键点:
- Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
- 只要有一个实例率先改变状态,整个promise的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给promise的回调函数。
// 只要有一个promise成功整个race就resolve
Promise.race = function(arr){
return new Promise((resolve, reject) => {
for (let i = 0; i < arr.length; i++){
let item = arr[i];
if (isThenable(item)){
try {
let then = item.then;
if (typeof then === 'function') {
then(resolve, reject);
} else {
resolve(item);
}
} catch (e) {
reject(e);
}
} else {
resolve(item);
}
}
});
}
思维导图
promise.then
resolvePromise
测试
如何测试我们自己的Promise是否符合Promises/A+规范呢? 开源社区提供了一个包用于测试我们的代码:promises-aplus-tests
1首先 安装 promises-aplus-tests:npm install promises-aplus-tests
2然后 添加一下代码:
// 实现一个promise的延迟对象 defer, 在我们测试的时候需要用到
MyPromise.defer = MyPromise.deferred = function() {
let deferred = {};
deferred.promise = new MyPromise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred;
}
module.exports = MyPromise;
3 更改package.json文件
// promises-aplus-tests 加上你的js文件
"scripts": {
"test": "promises-aplus-tests test-promise.js",
},
4 执行命令
npm run test
5 检查测试结果,如果全是绿色,说明你的代码符合Promises/A+规范(没有完成的promise.all() 和 promise.rase()不会影响我们已有代码的测试结果) 屏幕快照 2021-04-27 08.54.12 PM
最终代码(含测试代码):
希望这篇文章能够帮助同学们更好的理解Promise的原理,有疑惑或者不懂的地方请随时留言讨论,如果这篇文章有帮助到同学们,请留一个赞吧。