ES6到ES10— Promise的基本使用和手动封装

805 阅读10分钟

前言

该章为个人ES6Promise知识点笔记,主要记录Promise的基本api和注意点,最后附上利用Class封装的简单Promise

内容

1. Promise基本介绍

  1. 什么是Promise? Promise是ES6中新的语法,一种新的异步编程的解决方案;
    使用上来说,Promise是一个构造函数,封装一个异步操作对象并可以获得其结果

注意:为什么要有新的解决方案,旧的解决方案是什么为什么不好

  1. 旧的解决方案是使用回调函数的方式,不利于阅读也容易产生回调地狱,callBack嵌套callBack

  2. 旧方案不够灵活,必须先指定回调函数,Promise可以先执行异步,异步结束后再指定回调

  3. Promise的基本工作流程

Promise有三种状态 pending(初始)、resolved(成功)、rejected(失败)

const p1 = new Promise((resolve, reject) => {
    //1. Promise在创建之初,此时Promise处理pending状态
    setTimeout(()=>{
        //3. 异步操作之后调用了resolve,此时Promise进入resolved状态
        resolve(1)
    },1000)
})
//2. 指定Promise的onResolved,onRejected回调函数
p1.then(
    value=>{//onResolved函数,成功的回调
        //4. Promise随着resolve的调用,改变为成功状态后,去异步调用对应的onResolved成功回调函数
        console.log(value)
    },
    reason=>{//onRejected函数,失败的回调
        console.log(reason)
    }
)

2. Promise基础api

下面介绍Promsie最常用的基础api和展示

  1. new Promise() 需要传入一个执行器函数,返回一个Promise对象,根据执行器函数内部调用resolve或reject,从而改变状态,触发回调函数
    new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve(1)
        },1000)
    })
  1. then 传入两个回调函数,第一个是成功的回调函数,第二个是失败的回调函数,返回一个新的Promise对象,Promise根据其状态是成功还是失败从而选择调用这里的哪一个回调函数,返回的新Promise对象会根据回调函数的执行结果,从而决定自己的状态执行新一轮的回调,最终完成链式调用
    new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve(1)//这里调用了ressolve Promise会进入成功状态,会调用.then中传入的成功回调,反之调用reject就会调用失败的回调
        },1000)
    }).then(
        //成功的回调
        value=>{
            console.log(value)//1
            return 2 //这里返回了2那么链式调用时,下一个成功的回调参数就能接受到2
        },
        //失败的回调
        reason=>{
        }
    ).then(
        value=>{
            console.log(value)//2
            throw Error(3)//当这里抛出了错误,或者返回一个失败状态的Promise,那么下一次就调用.then中失败的回调
        },
        reason=>{
        }
    )
    .then(
        value=>{
        },
        reason=>{
            console.log(reason)//3 这里就调用了失败的回调
        }
    )
  1. catch 传入一个回调函数,当Promise在执行之前的回调中,发生了错误或者失败了,将执行这个回调,实际上就相当于.then的一个语法糖
    //伪代码示意
    .catch(reason=>{
        //xxx
    })
    //等效于
    .then(undefind,reason=>{
        //xxxx
    })
  1. all 接受一个数组,返回一个Promise对象,数组内可以是多个Promise对象或者是非Promise对象的数据(一般都为Promise对象,如果不是Promsie对象,那么默认是以该值返回一个成功的Promsie对象),当数组内所有Promise对象状态为成功那么返回的Promsie对象进入成功状态回调的参数是数组内Promise对象的成功回调参数,如果有一个失败了,那么返回的Promsie就会进入失败状态,回调的参数就是数组内第一个失败的Promise的原因
const p1 = new Promise((resolve, reject) => {
  resolve(1)
})
const p2 = new Promise((resolve, reject) => {
  resolve(2)
})
const p3 = new Promise((resolve, reject) => {
  resolve(3)
})
Promise.all([p1, p2, p3])
    .then(
    value => { console.log(value)},//全部成功all进入成功回调,回调参数是[1,2,3]
    reason => { console.log(reason) })

另一种情况

const p1 = new Promise((resolve, reject) => {
  reject(1) //p1进入失败回调 回调的参数是1
})
const p2 = new Promise((resolve, reject) => {
  resolve(2)
})
const p3 = new Promise((resolve, reject) => {
  resolve(3)
})
Promise.all([p1, p2, p3])
    .then(
    value => { console.log(value)},
    reason => { console.log(reason) })//没有全部成功all进入失败回调,reason=1
  1. race 接受一个数组,返回一个Promise对象,数组内可以是多个Promise对象或者是非Promise对象的数据(一般都为Promise对象,如果不是Promsie对象,那么默认是以该值返回一个成功的Promsie对象),根据数组内第一个进入回调的Promise进行判断,该Promise的状态就是返回Promsie的状态,该Promsie的回调函数的参数就是返回Promise回调函数的参数
const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resovle(1) //最先进入回调 成功回调 回调参数为1
    }, 1000);
})
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resovle(2)
    }, 2000);
})
const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resovle(3)
    }, 3000);
})
Promise.race([p1, p2, p3])
    .then(
    value => { console.log(value)},//成功回调 回调参数为1
    reason => { console.log(reason) })
  1. resolve、reject 接受一个Promise或者一个非Promise对象的值
    a. 调用resolve返回一个Promise,如果传入非Promsie对象数据,那么返回一个成功的Promsie,成功的回调参数就是传入的非Promise对象数据,如果传入的是一个Promise对象,那么返回的Promise根据传入的Promise的状态和回调参数,决定自己的状态和回调参数
    b. 调用reject返回一个失败的Promise,失败回调的参数就是,你传入的参数
    Promise.resolve(1) //返回成功Promise回调参数是1
    Promise.reject(2) //返回失败的Promise回调参数是2
    Promise.resolve(Promise.resolve(1)) //返回成功的Promise回调参数是1 
    Promise.resolve(Promise.reject(2)) //返回失败的Promise回调参数是2 

3. 中断Promise链

有些情况下我们需要中断Promise.then()的回调链,虽然很少见,但是笔者在封装axios拦截器取消请求的时候确实遇到了这样的情况,先贴上代码

    ... //这里就用伪代码示意了
    .then(
        value=>{
            return new Promise(()=>{}) //返回一个初始状态的Promise就行了
        },
        reason=>{
            return new Promise(()=>{})//返回一个初始状态的Promise就行了
        }
    )
    .then(
        //后续的.then不会执行了
    )

原理:.then时会返回一个Promise,在这个Promise中我们需要去调用reject或者resolve去改变Promise的状态触发回调函数,用来完成链式调用,但是如果是返回了一个初始状态的Promsie,没有调用reject或者resolve,那么后续的.then都会去等待这个Promsie去改变状态从而触发回调函数,从而达到中断Promsie;

4. async/await和Promise

简介

  1. async/await被称为最终的处理异步回调的方案,通常和Promise配套使用,以下展示
    //模拟请求函数
    function getMessage(){
        return new Promise((resolve,reject)=>{
            setTimeout(()=>{
                resolve(111)//假设1秒后请求到了数据111
            },1000)
        })
    }
    //在另一个函数中我们需要使用到请求下来的数据,以往我们需要在.then中处理
    async function fn1 (){
        const data = await getMessage() //data=111 这行代码是同步执行的,此时data=111
    }

以上为async/await的基本使用,一般而言最常见的场景就是这样,async/await的使用使得我们不必频繁的用.then处理回调,编码上可以使用同步的方式编码也能去处理异步的回调,但是有以下几点需要稍微注意

注意

  1. async/await 一般而言是配套使用,但是也不是必须的,async可以单独使用在函数前面,函数内不必一定使用await,但是反之不行,如果使用await那么函数前必须带上async,否则会报错

  2. async 用于函数前时,这个函数将不再返回原有的值,函数将直接返回一个Promise,这个Promise的状态由原函数的返回值决定,大致的返回规则如下: a. 如果原函数返回了一个非Promise的值,那么async将会使得函数返回一个成功的Promise,成功回调的值就是原函数的返回值
    b. 如果原函数返回了一个Promise值,那么async将会使得函数返回一个Promise,这个Promise的状态和值由返回的Promise决定
    c. 如果函数执行报错,那么async将会使得函数返回一个失败的Promise,这个Promise的值就是报错的原因

  3. await的后面一般是个表达式或Promise对象,但是也可以不是,如果是Promise对象,await就会获取Promise对象回调后的结果,如果不是那么就直接返回原值

5. 手动封装Promise

最后是手动封装一个简易的Promise,这个也是前端初级进阶需要能够手撕的代码之一,笔者的Promise是利用ES6的Class封装的,实现了简单的构造函数、then、catch、reject、resolve、all、race这几个方法,方法内贴出了部分注释仅供参考

(function (window) {
  //利用闭包创建promise的三种基础状态
  const PENDING = 'pending';
  const RESOLVED = 'resolved';
  const REJECTED = 'rejected';
  
  //Promise类
  class Promise {
    //Promise构建时需要传入一个执行器函数
    constructor(excutor) {
      this.status = PENDING;//初始状态为PENDING
      this.data = undefined;//初始时数据不会被定义,这个需要在调用回调函数时被赋值
      this.callBacks = []; //[{onResolved ,onRejected}] 回调队列
      
      //1. 封装一个handleCallBack函数用于处理进入不同回调,
      //2. status的值只能为 RESOLVED 或者 REJECTED
      //3. 这里需要使用箭头函数,不然函数调用时this会默认指向window
      //4. 思路:当进入reject或者resolve回调时,Promise会改变到对应状态,同时将传入回调函数的参数保存,最后去执行回调队列里对应状态的函数
      const handleCallBack = (status, data) => {
        //promise的状态只能被改变一次,如果已经发生改变,那么直接返回
        if (this.status !== PENDING) {
          return
        }
        this.status = status === RESOLVED ? RESOLVED : REJECTED;//改变Promsie状态
        this.data = data//保存回调参数
        //回调函数是需要被异步执行的,这里使用了setTimeout
        setTimeout(() => {
          if (this.callBacks.length > 0) {
            //遍历回调队列,根据Promise的状态去执行相对应成功或者失败的回调
            this.callBacks.forEach(callBackObj => {
              status === RESOLVED ? callBackObj.onResolved(data) : callBackObj.onRejected(data)
            });
          }
        }, 0);
      }
      function resolve (value) {
        handleCallBack(RESOLVED, value)
      }
      function reject (reason) {
        handleCallBack(REJECTED, reason)
      }
      excutor(resolve, reject)
    }
    //实例方法
    
    /**
    *思路:
    * 1. then 返回的是一个Promise对象
    * 2. 如果当前Promise的状态是pending.那么将参数专递的回调函数,推入回调队列中,并在执行后改变Promise的状态
    * 3. 如果当前状态不是pending,那么调用对应的回调函数,改变Promise状态并判断其返回结果类型,是Promise那么该Promise的状态和回调参数就是本次返回Promise的状态和参数,如果不是Promise那么直接进入成功回调,回调函数的参数就是本次回调函数的结果
    * 4. 其中还需注意异常和未传回调函数这种情况的处理
    * */
    then (onResolved, onRejected) {
      const that = this
      onResolved = typeof onResolved === 'function' ? onResolved : value => value;
      onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
      return new Promise((resolve, reject) => {
        function handle (callBack) {
          try {
            let result = undefined;
            if (!callBack) {
              result = that.status === RESOLVED ? onResolved(that.data) : onRejected(that.data)
            } else {
              result = callBack(that.data);
            }
            if (Promise.isPromise(result)) {
              result.then(resolve, reject);
            } else {
              resolve(result);
            }
          } catch (error) {
            reject(error);
          }
        }
        if (that.status === PENDING) {
          that.callBacks.push({
            onResolved: function () {
              handle(onResolved);
            },
            onRejected: function () {
              handle(onRejected);
            }
          })
        } else {
          setTimeout(() => {
            handle();
          }, 0);
        }
      })

    }
    catch (onRejected) {
      return this.then(undefined, onRejected);
    }
    //静态方法
    static resolve (value) {
      return new Promise((resolve, reject) => {
        if (Promise.isPromise(value)) {
          value.then(resolve, reject);
        } else {
          resolve(value);
        }
      })
    }
    static reject (reason) {
      return new Promise((resolve, reject) => {
        reject(reason);
      })
    }
    static all (promises) {
      if (!Array.isArray(promises)) {
        throw Error(`Parameters ${promises} of Promise static method .all must be array`)
      }
      const results = new Array(promises.length);
      let count = 0;
      return new Promise((resolve, reject) => {
        promises.forEach((promise, index) => {
          Promise.resolve(promise).then(
            value => {
              results[index] = value
              count++
              if (count == promises.length) {
                resolve(results)
              }
            },
            reason => {
              reject(reason)
            }
          )
        })

      })
    }
    static race (promises) {
      if (!Array.isArray(promises)) {
        throw Error(`Parameters ${promises} of Promise static method .all must be array`)
      }
      return new Promise((resolve, reject) => {
        promises.forEach((promise, index) => {
          Promise.resolve(promise).then(
            value => {
              resolve(value)
            },
            reason => {
              reject(reason)
            }
          )
        })

      })
    }
    static isPromise (obj) {
      return obj instanceof Promise
    }
  }
  window.$P = window.mPromise = Promise
}(window))

结语

笔者能力有限,整理中可能有疏漏和错误,如有发现恳请指正交流,若有幸被阅有所帮助,留下小爪爪鼓励一下吧