从0实现一个基本的Promise类(二)

332 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

前言

“手写Promise”系列作为前端常考点,深度检查了应聘者对Promise使用以及规范的理解,忏愧的是本人也是最近在完全通关这一模块。

此篇作为复习笔记,以及学习总结。

手写Promise包含以下知识点 👇:

  • Promise基础知识
  • Class 类
  • 改变this指向 (call、apply和bind)
  • 事件循环 Event Loop

其中Promise基础知识如果不了解或有遗忘的同学可以参考我这篇文章:[《简单明了的Promise基本知识点》]

看完本篇你能学到:

  1. 了解实现Promise的then方法
  2. 了解实现Promise中实现异步

实现then方法

在上文《从0实现一个基本的Promise类(一)》,我们实现了PromiseStatePromiseResultresolvereject,代码如下

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拒绝时会执行第二个函数参数。

vpPKUJ.png

因此我们就可以先给手写的then里面添加 两个参数

  • 一个是 onFulfilled 表示 “当状态为成功时”
  • 另一个是 onRejected 表示 “当状态为拒绝时”
 then(onFulfilled,onRejected){
        if (this.PromiseState == myPromise.FULFILLED){
            onFulfilled()
        }else if(this.PromiseState == myPromise.REJECTED){
            onRejected()
        }
    }

状态不可变

对比下原生的then方法,

vCq6T1.png 我们发现当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 规范如果 onFulfilledonRejected 不是函数,就忽略他们,所谓“忽略”并不是什么都不干,对于onFulfilled来说“忽略”就是将value原封不动的返回,对于onRejected来说就是返回reasononRejected因为是错误分支,我们返回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方法中执行的onFulfilledonRejected都是异步执行的。

let num = 0
let promies1 = new Promise((resolve,reject)=>{
    num++
    resolve(num)
    num++
}).then((res)=>{
    console.log('then:',res)
})
console.log(num)

想想以上代码该以何种顺序在控制台中输出🤔?

答案是:2then: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)

最后输出是:2then:1,证明添加异步行为成功🤣

Next

由于将整个Promise细节堆在一起讲解篇幅会过长,我将文章也拆分为了五个小段进行描述。目的是为了让大家能够理解到不同的阶段,能够充分的消化并在实战中使用。 下一期从0实现一个基本的Promise类(三)会讲述

  • pending状态下执行then方法的回调保存

    \