携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
前言
“手写Promise”系列作为前端常考点,深度检查了应聘者对Promise使用以及规范的理解,忏愧的是本人也是最近在完全通关这一模块。
此篇作为复习笔记,以及学习总结。
手写Promise包含以下知识点 👇:
- Promise基础知识
- Class 类
- 改变this指向 (call、apply和bind)
- 事件循环 Event Loop
其中Promise基础知识如果不了解或有遗忘的同学可以参考我这篇文章:[《简单明了的Promise基本知识点》]
看完本篇你能学到:
- 了解实现
Promise的then方法 - 了解实现
Promise中实现异步
实现then方法
在上文《从0实现一个基本的Promise类(一)》,我们实现了
PromiseState、PromiseResult、resolve、reject,代码如下
class myPromise{
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor(func){
this.PromiseState = myPromise.PENDING
this.PromiseResult = undefined
func(this.resolve.bind(this),this.reject.bind(this))
}
resolve(result){
if(this.PromiseState == myPromise.PENDING){
this.PromiseState = myPromise.FULFILLED
this.PromiseResult = result
console.log(result)
}
}
reject(reason){
if (this.PromiseState == myPromise.PENDING){
this.PromiseState = myPromise.REJECTED
this.PromiseResult = reason
}
}
}
在开始之前我们先回忆下原生的then方法:
let promise1 = new Promise((reslove,reject)=>{
resolve('success')
})
promise1.then(result =>{
console.log(result)//'sucess'
},reason => {
console.log(reason)
})
then方法可以传入两个参数,这俩个参数都是函数。当前状态为fulfilled成功时会执行第一个参数函数,为rejected拒绝时会执行第二个函数参数。
因此我们就可以先给手写的then里面添加 两个参数:
- 一个是
onFulfilled表示“当状态为成功时” - 另一个是
onRejected表示“当状态为拒绝时”
then(onFulfilled,onRejected){
if (this.PromiseState == myPromise.FULFILLED){
onFulfilled()
}else if(this.PromiseState == myPromise.REJECTED){
onRejected()
}
}
状态不可变
对比下原生的then方法,
我们发现当resolve先执行后,后续的reject就不会再执行了。也就是Promise状态只能发生一次变化的特性。并且resolve中传递的
PromiseResult,当执行传进来的 onFulfilled 函数,并且为onFulfilled函数传入前面保留的PromiseResult属性值。
then(onFulfilled,onRejected){
if (this.PromiseState == myPromise.FULFILLED){
onFulfilled(this.PromiseResult)
}else if(this.PromiseState == myPromise.REJECTED){
onRejected(this.PromiseResult)
}
}
执行异常 throw
在new Promise的时候,执行函数里面如果抛出错误,会触发then方法的第二个参数,即rejected状态的回调函数。wait,执行异常抛错,不是用catch()方法去接吗?为什么这里又说 是会触发then方法的第二个参数,即rejected状态的回调函数?
catch()
catch()方法返回一个Promise,并且处理拒绝的情况- 它的行为与调用
Promise.prototype.then(undefined, onRejected)相同 Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// 等同于
p.then(
null,
err=> {console.log(err)}
)
// 等同于
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));
◾ 注意看下面的例子 :
const promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.catch(function(error) {
console.log(error);
});
// Error: test
上面代码中,promise抛出一个错误,就被catch()方法指定的回调函数捕获。注意,上面的写法与下面两种写法是等价的。
// 写法一
const promise = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
// 写法二
const promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
比较上面两种写法,可以发现reject()方法的作用,等同于抛出错误。这一点很重要,因为我们手写Promise就是用try/catch来处理异常,用的就是上面的思想。
◾ 一般来说,不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
上面代码中,第二种写法要好于第一种写法,理由是第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch()方法,而不使用then()方法的第二个参数。
由此我们注意到了要去主动捕捉执行函数中的错误,并在错误时执行then方法中的onRejected函数。
constructor(func){
this.PromiseState = myPromise.PENDING
this.PromiseResult = undefined
try{
func(this.resolve.bind(this),this.reject.bind(this))
}catch (e){
this.reject(e)
}
}
因此,我们在构造函数中加上了捕捉错误的判断。在执行时如果发生错误就会抛出异常,并执行reject函数。这样我们用then方法时/catch方法时就能够正常捕捉到错误了。
参数校验
在原生的Promise中then方法中的俩个函数参数有参数类型的校验:
Promise规范如果onFulfilled和onRejected不是函数,就忽略他们,所谓“忽略”并不是什么都不干,对于onFulfilled来说“忽略”就是将value原封不动的返回,对于onRejected来说就是返回reason,onRejected因为是错误分支,我们返回reason应该throw一个Error。
then(onFulfilled,onRejected){
// 如果onFulfilled参数是一个函数,就把原来的onFulfilled内容重新赋值给它,如果onFulfilled参数不是一个函数,就将value原封不动的返回
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : value=>value
//如果onRejected参数是一个函数,就把原来的onRejected内容重新赋值给它,如果onRejected参数不是一个函数,就throw一个Error
onRejected = typeof onRejected == 'function' ? onRejected : reason =>{throw reason}
if (this.PromiseState == myPromise.FULFILLED){
onFulfilled(this.PromiseResult)
}else if(this.PromiseState == myPromise.REJECTED){
onRejected(this.PromiseResult)
}
}
实现异步
在原生的promise中我们知道then方法中执行的onFulfilled和onRejected都是异步执行的。
let num = 0
let promies1 = new Promise((resolve,reject)=>{
num++
resolve(num)
num++
}).then((res)=>{
console.log('then:',res)
})
console.log(num)
想想以上代码该以何种顺序在控制台中输出🤔?
答案是:2 ,then:1
then中的执行res得到的是第一次num++后的值,并在之后才执行。
同样的我们在myPromise中也添加异步特点😎😎,在这里使用的是setTimeout
then(onFulfilled,onRejected){
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : value=> value
onRejected = typeof onRejected == 'function' ? onRejected : reason =>{ throw reason }
if (this.PromiseState == myPromise.FULFILLED){
setTimeout(()=>{
onFulfilled(this.PromiseResult)
})
}else if(this.PromiseState == myPromise.REJECTED){
setTimeout(()=>{
onRejected(this.PromiseResult)
})
}
}
目前已完成的代码:
class myPromise{
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor(func){
this.PromiseState = myPromise.PENDING
this.PromiseResult = undefined
try{
func(this.resolve.bind(this),this.reject.bind(this))
}catch (e){
this.reject(e)
}
}
resolve(result){
if(this.PromiseState == myPromise.PENDING){
this.PromiseState = myPromise.FULFILLED
this.PromiseResult = result
}
}
reject(reason){
if (this.PromiseState == myPromise.PENDING){
this.PromiseState = myPromise.REJECTED
this.PromiseResult = reason
}
}
then(onFulfilled,onRejected){
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : value => value
onRejected = typeof onRejected == 'function' ? onRejected : reason =>{ throw reason }
if (this.PromiseState == myPromise.FULFILLED){
setTimeout(()=>{
onFulfilled(this.PromiseResult)
})
}else if(this.PromiseState == myPromise.REJECTED){
setTimeout(()=>{
onRejected(this.PromiseResult)
})
}
}
}
let num = 0
let promies1 = new myPromise((resolve,reject)=>{
num++
resolve(num)
num++
}).then((res)=>{
console.log('then:',res)
})
console.log(num)
最后输出是:2 ,then:1,证明添加异步行为成功🤣
Next
由于将整个Promise细节堆在一起讲解篇幅会过长,我将文章也拆分为了五个小段进行描述。目的是为了让大家能够理解到不同的阶段,能够充分的消化并在实战中使用。 下一期从0实现一个基本的Promise类(三)会讲述
-
pending状态下执行then方法的回调保存
\