本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Promise 作为如今前端解决异步问题的统一方案,在面试中已几乎是必考题。为深入理解 Promise,了解其内部的实现原理,本文将带你由浅入深,从零开始手写一个 Promise。
Promise回顾
在进入主题前,我们先来回顾一下promise的基本用法:
// 在new的时候需要传入回调函数,通过函数的执行结果决定执行的是resolve还是reject
const promise = new Promise((resolve, reject) => {})
// 通过then来获取成功的结果,then接收两个回调函数为参数,第一个参数接收成功状态,第二个参数接收失败状态
promise.then((res) => {}, (error) => {})
// 通过catch 捕获失败回调,还可以链式调用
promise.then(onFulfilled).catch(onRejected)
promise有三种状态:
- 初始状态:
pending - 成功状态:
fullfilled - 失败状态:
rejected
状态一旦更改便不可逆,当new Promise()执行之后,promise对象的状态会被初始化为pending,这个状态是初始化状态。new Promise()这行代码,括号里的内容是同步执行的,函数执行调用resolve,promise的状态会被自动修改为fullfilled,反之则为reject。
Promise实现
Promise雏形
通过上面我们已经知道,promise是通过new来构造,并会传入回调函数,那么最基础的promise就可以写出来了:
class Commitment {
constructor(fun) {
fun(resolve,reject)
}
}
这里通过construtor接收传进来的函数,并执行。然而执行结果的状态我们是不知道,我们需要对状态进行保存,并定义的resolve和reject
class Commitment {
static PENDING = "待定";
static FULFILLD = "成功";
static REJECTED = "失败";
constructor(fun) {
//初始化状态
this.status=PENDING
fun(resolve,reject)
}
resolve() {
// 进入reslove,将状态修改为成功
if (this.status === Commitment.PENDING) {
this.status = Commitment.FULFILLD;
}
}
reject() {
// 进入reject,将状态修改为失败
if (this.status === Commitment.PENDING) {
this.status = Commitment.REJECTED;
}
}
到了这里,比较基础的雏形已经搭建完成了。然而需要注意的是,resolve和reject是可以传参数,并且这个参数会传递到then/catch中,下面对参数进行保存,定义then函数
class Commitment {
……
constructor(fun) {
//定义为null,当执行resolve或reject时会进行赋值
this.result=null
fun(resolve,reject)
}
resolve(result) {
if (this.status === Commitment.PENDING) {
……
//对参数进行赋值
this.result=result
}
}
reject(result) {
if (this.status === Commitment.PENDING) {
……
this.result=result
}
//then接收两个函数,成功和失败的回调函数
then(onFULFILLED, onREJECTED) {
//通过状态判断then执行相应的函数
if (this.status === Commitment.FULFILLD) {
//执行成功的回调,并将result作为参数传递
onFULFILLED(this.result);
}
if (this.status === Commitment.REJECTED) {
onREJECTED(this.result);
}
}
}
仅仅这么写是不对的,需要注意的是我们使用Commitment一般都是在外部进行调用,即外部通过new Commitment创建实例,进而调用constructor和resolve,然而resolve中的this已经是外部环境的this,而不是Commitment,从而报错,我们通过bind给实例的reslove绑定this为当前对象:
fun(this.resolve.bind(this), this.reject.bind(this));
异步回调
在以上我们只是完成了基本的架构,还没有涉及到promise的核心:异步,说到异步,我们就得了解事件循环机制,我们用例子粗略的回顾一下,看看以下的代码会输出什么:
console.log("第一步");
let p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("第二步");
console.log("第三步")
},0);
})
p.then(result=>{
if (this.PromiseState === 'fulfilled'){
console.log('fulfilled')
}
console.log(result);
})
console.log("第五步");
//输出:第一步、第五步、第三步、第二步
如果你还不太了解事件循环机制的话,可以参考:事件循环。通过这个例子,我们可以很清晰的知道,在Promise中then里面的函数体总是在最后执行,也就是我们所说的宏任务,那么我们可以直接在then中加上setTimeout,使其变成异步调用。
then(onFULFILLED, onREJECTED) {
if (this.status === Commitment.FULFILLD) {
setTimeout(() => {
onFULFILLED(this.result);
});
}
if (this.status === Commitment.REJECTED) {
setTimeout(() => {
onREJECTED(this.result);
});
}
}
在这里是有个问题的,使用我们构造的Promise的时候,如果使用的是setTimeout,我们并不能确保,到then里面的状态一定会变成resolve或reject,请看以下例子:
console.log("第一步");
let p = new Commitment((resolve,reject)=>{
setTimeout(()=>{
resolve("第二步");
console.log("第三步")
},1000);
})
p.then(result=>{
console.log(result);
})
console.log("第五步");
//输出:第一步、第五步、第三步
我们发现第二步是并没有执行的,这里是因为执行到then的时时候,resolve并没有执行,更贴切一点的说法:then的方法被先执行了,而我们在then里面仅处理resovle和reject的情况,对pending的状态并没有进行做出处理,进而没有打印出第二步。
我们不能确保then一定会在resolve后执行,这是由定义所决定的,但是我们能保证then的函数体在resolve后执行。当我们在then中检测到Promise状态为pending的时候,我们使用队列来保存then里面要执行的函数,等执行resolve后,再把队列里面的函数取出来进行执行。
class Commitment {
//……
constructor(fun) {
//……
// 定义数组(先进先出),保存then里面的函数参数
this.resolveCallbacks = [];
this.rejectCallbacks = [];
}
resolve(result) {
setTimeout({
// 执行完resolve,取出任务队列中函数
this.resolveCallbacks.forEach((callback) => {
callback(result);
})
})
}
then(onFULFILLED, onREJECTED) {
// 状态为pending先保存
if (this.status === Commitment.PENDING) {
this.resolveCallbacks.push(onFULFILLED);
this.rejectCallbacks.push(onREJECTED);
}
//……
}
}
细心的同学已经发现,这里在执行resolve的时候,已经加上了setTimeout,这是因为resovle执行的本身就是异步任务。至此,一个粗略的Promise已经完成了。
边缘处理
回调不完整
在真正的promise中,当用户传递的回调函数为undefined的时候,其默认为回调赋值为空函数。接下来对我们的Promise进行处理
then(onFULFILLED, onREJECTED) {
// 如果传递的resolve和reject没有定义,就默认空函数
onFULFILLED =typeof onFULFILLED === "function" ? onFULFILLED : () => {};
onREJECTED = typeof onREJECTED === "function" ? onFULFILLED : () => {};
}
throw
Promise执行过程,如果我们使用throw抛出错误的话,Promise会按reject处。继续改造我们的Promise,constructor中进行错误检测
constructor(fun) {
try {
// 外部调用this会发生变化,需要绑定内部的this
fun(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
链式调用
我们只需在then中返回一个promise对象即可:
then{
return new Commitment((resolve,reject)=>{
……
}
}
状态不可变
在Promise中如果状态一旦改变,那么后续的使用中,状态是不能改变的
resolve(value) {
// status是不可变的
if (this.status !== Commitment.PENDING) return
}
reject(reason) {
// status是不可变的
if (this.status !== Commitment.PENDING) return
}
完整代码
class Commitment {
static PENDING = "待定";
static FULFILLD = "成功";
static REJECTED = "失败";
constructor(fun) {
this.status = Commitment.PENDING;
this.result = null;
this.resolveCallbacks = [];
this.rejectCallbacks = [];
try {
fun(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
resolve(result) {
if (this.status !== Commitment.PENDING) return
setTimeout(() => {
if (this.status === Commitment.PENDING) {
this.status = Commitment.FULFILLD;
this.result = result;
this.resolveCallbacks.forEach((callback) => {
callback(result);
});
}
});
}
reject(result) {
if (this.status !== Commitment.PENDING) return
setTimeout(() => {
if (this.status === Commitment.PENDING) {
this.status = Commitment.REJECTED;
this.result = result;
this.rejectCallbacks.forEach((callback) => {
callback(result);
});
}
});
}
then(onFULFILLED, onREJECTED) {
return new Commitment((resolve, reject) => {
onFULFILLED =typeof onFULFILLED === "function" ? onFULFILLED : () => {};
onREJECTED = typeof onREJECTED === "function" ? onFULFILLED : () => {};
if (this.status === Commitment.PENDING) {
this.resolveCallbacks.push(onFULFILLED);
this.rejectCallbacks.push(onREJECTED);
}
if (this.status === Commitment.FULFILLD) {
setTimeout(() => {
onFULFILLED(this.result);
});
}
if (this.status === Commitment.REJECTED) {
setTimeout(() => {
onREJECTED(this.result);
});
}
});
}
}
Promise方法
all
Promise.all() 是一个内置的辅助函数,接受一组 promise(或者一个可迭代的对象),并返回一个promise
static all(promises) {
const result = []
let count = 0
return new Commitment((resolve, reject) => {
const addData = (index, value) => {
result[index] = value
count++
if (count === promises.length) resolve(result)
}
promises.forEach((promise, index) => {
if (promise instanceof Commitment) {
promise.then(res => {
addData(index, res)
}, err => reject(err))
} else {
addData(index, promise)
}
})
})
}
race
字面意思是赛跑,接收一个Promise数组,哪个Promise,最快得到结果,就返回那个结果,无论成功失败。当数组中如有非Promise项时,直接返回该项
static race(promises) {
return new Commitment((resolve, reject) => {
promises.forEach(promise => {
if (promise instanceof Commitment) {
promise.then(res => {
resolve(res)
}, err => {
reject(err)
})
} else {
resolve(promise)
}
})
})
}
allSettled
参数是一组包含Promise实例的数组,返回值是一个新的Promise实例,其实例在调用then方法中的回调函数的参数仍是一个数组。数组的每一项是一个包含status和value的一组对象。status代表对应的参数实例状态值,取值只有fulfilled和rejected,通过这个状态,我们能很好的过滤掉其中reject的Promise,解决了all方法的局限性。
static allSettled(promises) {
return new Commitment((resolve, reject) => {
const res = []
let count = 0
const addData = (status, value, i) => {
res[i] = {
status,
value
}
count++
if (count === promises.length) {
resolve(res)
}
}
promises.forEach((promise, i) => {
if (promise instanceof Commitment) {
promise.then(res => {
addData('fulfilled', res, i)
}, err => {
addData('rejected', err, i)
})
} else {
addData('fulfilled', promise, i)
}
})
})
}
any
和all相反,有一个成功便返回,全部失败就报错,非Promise优先返回
static any(promises) {
return new Commitment((resolve, reject) => {
let count = 0
promises.forEach((promise) => {
promise.then(val => {
resolve(val)
}, err => {
count++
if (count === promises.length) {
reject(new AggregateError('All promises were rejected'))
}
})
})
})
}
结语
以上便是关于手写Promise的全部内容,大概的流程即是如此,但和真正的Promsie来说还是大巫见小巫了,但对于我们学习来说已经是够用了。