Promise
面试:说说你对promise的理解?
我:经常用,promise是一个构造函数,用new来声明,promise传入两个参数resove和reject,then接收resolve里边的参数,catch接收reject里边的参数。
完了?? 完了。。。
好好看看吧。怎么才能答出面试官比较满意的答案呢?我们先手写一个深入了解一下吧。
基本使用
首先要了解:then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。
看一个promise的基本使用:
const promise = new Promise((resolve, reject) => {
resolve('success');
reject('error');
})
promise.then(res => {
console.log(res);
}, err => {
console.log(err);
});
一、首先我们先实现以下基本原理:
- Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行
- Promise 会有三种状态
- Pending 等待
- Fulfilled 完成
- Rejected 失败
- 状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改;
- Promise 中使用 resolve 和 reject 两个函数来更改状态;
- then 方法内部做但事情就是状态判断
- 如果状态是成功,调用成功回调函数
- 如果状态是失败,调用失败回调函数
// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
// executor 是一个执行器,进入会立即执行
executor(this.resolve, this.reject);
}
status = PENDING;
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
// resolve和reject为什么要用箭头函数?
// 如果直接调用的话,普通函数this指向的是window或者undefined
// 用箭头函数就可以让this指向当前实例对象
// 更改成功后的状态
resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
}
}
// 更改失败后的状态
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
}
}
then(onFulfilled, onRejected) {
// 判断状态
if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
}
}
}
// 测试我们上边写的promise
const promise = new MyPromise((resolve, reject) => {
resolve('success');
reject('error');
})
promise.then((res) => {
console.log(res);
}, (err) => {
console.log(err);
})
二、处理异步
如果我们要执行异步操作,如下,就有问题了
const promiseAsync = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000);
})
promiseAsync.then(value => {
console.log('resolve', value)
}, reason => {
console.log('reject', reason)
})
结果没有打印信息
分析:主线程代码立即执行,setTimeout 是异步代码,then 会马上执行,这个时候判断 Promise 状态,状态是 Pending,然而之前并没有判断等待这个状态
改造思路,处理一下Pending状态
// 新增存储成功回调函数
onFulfilledCallback = null;
// 存储失败回调函数
onRejectedCallback = null;
// 如果是异步的话就会先走then,再走resolve/reject,所以先存一下成功/失败的回调
then(onFulfilled, onRejected) {
// 判断状态 新增判断PENDING状态
if (this.status === PENDING) {
onFulfilledCallback = onFulfilled;
onRejectedCallback = onRejected;
} else if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
}
}
resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 如果有成功回调函数,就执行
onFulfilledCallback && onFulfilledCallback(value);
}
}
// 更改失败后的状态
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// 如果有失败回调函数,就执行
onRejectedCallback && onRejectedCallback(reason);
}
}
改造完成,让我们试一下上边的代码,有结果了:1s后输出 resolve success
三、实现then多次调用处理函数
Promise 的 then 方法是可以被多次调用的。这里如果有三个 then 的调用,如果是同步回调,那么直接返回当前的值就行;如果是异步回调,那么保存的成功失败的回调,需要用不同的值保存,因为都互不相同。之前的代码需要改进。
先看一个🌰
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 2000);
})
promise.then(value => {
console.log(1)
console.log('resolve', value)
})
promise.then(value => {
console.log(2)
console.log('resolve', value)
})
promise.then(value => {
console.log(3)
console.log('resolve', value)
})
结果:
3
resolve success
分析:因为我们对onFulfilledCallback/onRejectedCallback是直接赋值,所以只会执行最后的一次
所以需要继续改造,保证所有的then全部执行(这里将储存成功/失败的回调放在了class里边,记得更改访问方式为this.onFulfilledCallback)
// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
// executor 是一个执行器,进入会立即执行
executor(this.resolve, this.reject);
}
// 新增存储成功回调函数,将其变成数组
onFulfilledCallback = [];
// 存储失败回调函数,将其变成数组
onRejectedCallback = [];
status = PENDING;
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
// resolve和reject为什么要用箭头函数?
// 如果直接调用的话,普通函数this指向的是window或者undefined
// 用箭头函数就可以让this指向当前实例对象
// 更改成功后的状态
resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 如果有成功回调函数,就执行
// 循环执行
while (this.onFulfilledCallback.length) {
// Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
this.onFulfilledCallback.shift()(value);
}
}
}
// 更改失败后的状态
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// 如果有失败回调函数,就执行
// 循环执行
while (this.onRejectedCallback.length) {
this.onRejectedCallback.shift()(reason);
}
}
}
// 如果是异步的话就会先走then,再走resolve/reject,所以先存一下成功/失败的回调
then(onFulfilled, onRejected) {
// 判断状态 新增判断PENDING状态
if (this.status === PENDING) {
this.onFulfilledCallback.push(onFulfilled);
this.onRejectedCallback.push(onRejected);
} else if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
}
}
}
再次执行,结果:
1
resolve success
2
resolve success
3
resolve success
完美~
四、实现then的链式调用(promise.then().then())
同样,我们先看一个例子:
const promise = new MyPromise((resolve, reject) => {
// 目前这里只处理同步的问题
resolve('success')
})
function other () {
return new MyPromise((resolve, reject) =>{
resolve('other')
})
}
promise.then(value => {
console.log(1)
console.log('resolve', value)
return other()
}).then(value => {
console.log(2)
console.log('resolve', value)
})
结果:
Uncaught TypeError TypeError: Cannot read property 'then' of undefined
思考:如果是then().then()的话,then()应该也是一个promise,so 改造then
...
// 如果是异步的话就会先走then,再走resolve/reject,所以先存一下成功/失败的回调
then(onFulfilled, onRejected) {
const resPromise = new MyPromise((resolve, reject) => {
// 判断状态 新增判断PENDING状态
if (this.status === PENDING) {
this.onFulfilledCallback.push(onFulfilled);
this.onRejectedCallback.push(onRejected);
} else if (this.status === FULFILLED) {
// 改造这里then嘛,肯定是成功的回调,获取成功回调的结果
const x = onFulfilled(this.value);
// 传入resolvePromise统一处理
resolvePromise(x, resolve, reject);
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
}
})
return resPromise;
}
function resolvePromise(x, resolve, reject) {
// 如果x是一个promise,那么就调用x的then方法,并且把resolve和reject传进去
if (x instanceof(MyPromise)) {
x.then(resolve, reject);
} else {
resolve(x);
}
}
结果:
1
resolve success
2
resolve other
五、then方法链式调用识别Promise是否返回自己
举例:
const promise = new Promise((resolve, reject) => {
resolve(100)
})
const p1 = promise.then(value => {
console.log(value)
return p1
})
使用原生Promise执行这个代码,会报类型错误
100
Uncaught (**in** promise) TypeError: Chaining cycle detected **for** promise #<Promise>
在我们的MyPromise实现一下:
class MyPromise {
......
then(onFulfilled, onRejected) {
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
const x = onFulfilled(this.value);
// resolvePromise 集中处理,将 promise2 传入
resolvePromise(promise2, x, resolve, reject);
} else if (this.status === REJECTED) {
onRejected(this.reason);
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
})
return promise2;
}
}
function resolvePromise(promise2, x, resolve, reject) {
// 如果相等了,说明return的是自己,抛出类型错误并返回
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
if(x instanceof MyPromise) {
x.then(resolve, reject)
} else{
resolve(x)
}
}
执行,结果报错了
resolvePromise(promise2, x, resolve, reject);
^
ReferenceError: Cannot access 'promise2' before initialization
分析:从报错来看,我们必须要等 promise2 完成初始化。这个时候我们就要用上宏微任务和事件循环的知识了,这里就需要创建一个异步函数去等待 promise2 完成初始化,我们采用创建微任务的技术方案 --> queueMicrotask
// MyPromise.js
class MyPromise {
......
then(onFulfilled, onRejected) {
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// ==== 新增 ====
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
// 获取成功回调函数的执行结果
const x = onFulfilled(this.value);
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject);
})
} else if (this.status === REJECTED) {
......
})
return promise2;
}
}
OK,改造完成,再次执行上边的结果:
1
resolve success
3
Chaining cycle detected for promise #<Promise>
还有很多功能没有实现,到这里就先不做后续处理了,感兴趣的可以参考: juejin.cn/post/694531…
总结:
- Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行
- Promise 会有三种状态
- Pending 等待
- Fulfilled 完成
- Rejected 失败
- 状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改;
- Promise 中使用 resolve 和 reject 两个函数来更改状态;
- then 方法内部做但事情就是状态判断
- 如果状态是成功,调用成功回调函数
- 如果状态是失败,调用失败回调函数
- Promise.then()执行之后得到的还是一个Promise,从而实现了链式调用
- Promise是用来解决回调地狱的
补充其缺点
- 首先,无法取消
Promise,一旦新建它就会立即执行,无法中途取消。 - 其次,如果不设置回调函数,
Promise内部抛出的错误,不会反应到外部。 - 第三,当处于
pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
如何取消正在执行的promise
真的有面试官问这种问题。。。
我们都知道promise一旦执行,则无法取消,所以需要我们自己封装了。我们可以借助Promise.race()的方法,如果不知道这个方法的,可以移步es6.ruanyifeng.com/?search=rac…
//传入一个正在执行的promise
function getPromiseWithAbort(p){
let obj = {};
//内部定一个新的promise,用来终止执行
let p1 = new Promise(function(resolve, reject){
obj.abort = reject;
});
obj.promise = Promise.race([p, p1]);
return obj;
}
调用
var promise = new Promise((resolve)=>{
setTimeout(()=>{
resolve('123')
},3000)
})
var obj = getPromiseWithAbort(promise)
obj.promise.then(res=>{console.log(res)})
//如果要取消
obj.abort('取消执行')