「重学js异步」原来Promise不过如此

点击蓝字 「前端小苑」关注我

本文导读

    

Promise是工作中非常常用,也是面试经常问到的知识点,本文从以下几方面重学 Promise,对它有更深入的理解,帮助我们更轻松的面对工作和面试。

1. Promise/A+ 规范解读

2. es6的 Promsie 介绍

3. 实现一个符合 Promsie/A+ 规范的 Promsie 和es6中新增的一些重要方法

4. 总结

作者 | 于是乎

编辑 | 桔子酱

01

Promise规范

在 Promise 出现之前,js中的异步是一直依靠回调函数来实现的,当多个异步操作有依赖关系关系的时候,就会出现多层嵌套,这样多个异步的操作形成了强耦合,牵一发而动全身,很难维护,这就是"回调函数地狱"。

Promise 规范最早是由社区提出和实现的,CommonJS组织先后提出了Promise/A、Promise/B、Promise/D 规范,后来一个叫 Promise/A+ 的组织,基于 Promise/A 规范进行修改和补充,制定了 Promise/A+ 规范。ES6中 Promise 的实现就是基于这个规范来实现的。

Promise/A+ 规范分为术语和要求两部分,下面我们看一下核心的规范内容(文章结尾参考文章中,给出了 Promise/A+ 原文地址,有兴趣可以参考)

术语

    promise 一个包含了兼容规范的then方法的对象或函数。

    thenable 一个包含了then方法的对象或函数。

    value 一个任何Javascript值。

    exception是由throw表达式抛出来的值。

    reason 是一个用于描述Promise被拒绝原因的值。

要求

Promise 状态 

Promise 必须是下面3个状态之一:pending, fulfilled 或 rejected。

状态只能由 pending 转到 fulfilled 或 rejected 状态,且状态不能再改变。

状态由 pending 转到 fulfilled ,必须有一个值,由pending转到rejected,必须有一个reason(原因)。

then方法

Promise 必须提供一个then方法来获取其值或原因。then方法接受两个参数:

promise.then(onFulfilled, onRejected)

1. onFulfilled 和 onRejected 都是可选的:如果 onFulfilled 或 onRejected 不是函数,直接忽略

2. 如果 onFulfilled 是函数, 它必须在状态变为 fulfilled 后调用,且不能调用多次,promise 的 value 为其第一个参数。
如果 onRejected 是函数, 它必须在状态变为  rejected 后调用,且不能调用多次,promise 的 reason 为其第一个参数。

3. 对于一个 promise,它的 then 方法可以调用多次。
当状态 fulfilled 后,所有 onFulfilled 都必须按照其注册顺序执行。当状态 rejected 后,所有 OnRejected 都必须按照其注册顺序执行。

02 

es6 的 Promise

下面我们参照 promise 规范,一起看下es6对promise的实现。

ES6 中,Promise 对象是一个构造函数,用来生产 Promise 实例。Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。

resolve:状态从 pending 变为resolved时调用,并将异步操作的结果作为参数;

reject: 状态从从 pending 变为 rejected 时调用,并将异步操作报出的错误作为参数。

const promise = new Promise(function(resolve, reject) {
  // ... some code


  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

then方法

then 方法定义在 Promise.prototype 上,Promise 实例生成以后,可以用 then 方法分别指定 resolved 状态和 rejected 状态的回调函数,其中 rejected 状态的回调函数(第二个参数)是可选的。

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then 方法会返回的是一个新的 Promise 实例,因此可以采用链式写法。

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 1);
  });
}


timeout(5000)
.then((value) => {
  console.log(value);
  return 2
})
.then((value) => {
  console.log(value)
})
.then((value) => {
  console.log(value)
})


// 1
// 2
// undefined

上面代码采用了then的链式写法,此时会按照次序依次调用回调函数。第一个回调函数完成以后,会将返回值2作为参数,传入第二个回调函数,所以第二个回调函数会打印2,由于第2个函数没有返回值,所以第3个回调会打印 undefined。

catch方法

catch 方法也定义在 Promise.prototype 上。是.then(null, rejection)或.then(undefined, rejection)的别名,用来指定发生错误时的回调函数。

$.post("/path1")
.then(() => {
    return $.post("/path2")
})
.then(() => {
    return $.post("/path3")
})
.catch((err) => {
    console.log(err)
})

上面的代码中,如果异步操作发生错误,状态变为 rejected 时,就会触发 catch 方法中的回调函数,另外,如果代码运行出现错误也会执行 catch 方法。

Promise 对象的错误总是会被下一个 catch 语句捕获,也就是说上面代码中的3个 Promise 中的任何一个出现错误,都能够被 catch 捕获。

catch 方法并不是 Promise/A+ 规范中规定的,下面我们要说的一些方法也都不是 Promise/A+ 中规定的,而是es6中新增的。

在使用时,使用 catch 方法,而不是then方法的第二个参数,来处理异常情况是更好的选择。第一,catch 可以捕获上面所有then方法中的执行错误。第二,使用 catch 方法更接近于同步的写法。

resolve()

将现有对象转为promise 对象

reject()

Promise.reject(reason)方法也会返回一个新的 promise 实例,该实例的状态为 rejected。

finally方法

ES2018 标准引入,用于指定不管 Promise 对象最后状态如何,都会执行的操作。

finally方法不依赖于 Promise 的执行结果,其回调函数不接受任何参数。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

上面代码,不管 promise 的状态怎么变化,最终都会执行 finally 方法中的回调。

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'done');
  });
}


timeout(100).then((value) => {
  console.log(value);
});

Promise.all

Promise.all() 方法可以将多个 Promise 实例包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

上面代码中p1,p2,p3都为promise实例。包装后的promise实例的状态由p1,p2,p3决定,有两种情况。

  1. 只有p1、p2、p3的状态全都变成 fulfilled,p的状态才会变成 fulfilled,此时传递给p回调函数的参数将是p1、p2、p3的返回值组成一个数组。

  2. 只要p1、p2、p3之中任意一个被 rejected,p的状态就变成 rejected,此时传递给p的回调函数的参数是第一个 reject 的实例的返回值。

Promise.race

Promise.race()方法也是将多个 Promise 实例包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1、p2、p3中任一个实例状态发生变化,p的状态就会改变。第一状态变化的 Promise 实例的返回值,就传递给p的回调函数的参数。

Promise.allSettled

ES2020 标准引入,同样接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。

const p = Promise.allSettled([p1, p2, p3]);

上面代码中,只有等到所有p1,p2,p3实例状态都发生改变,无论状态是fulfilled还是rejected,实例p的状态才会发生变化。

Promise.any

该方法目前是一个第三阶段的提案。该方法同样接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。

const p = Promise.any([p1, p2, p3]);

只要参数p1,p2,p3中任意一个变成 fulfilled 状态,包装后的实例p就会变成 fulfilled 状态;如果所有参数实例都变成 rejected 状态,包装实例就会变成 rejected 状态。

03

手写Promise

下面一步步自己实现一个 Promsie,目的并不是完全实现一个和es6一样功能的 Promise,而是通过手写一个 Promise,更深入的了解 Promise 的原理。

下面我们先实现一个符合 Promise/A+ 规范的 Promsie。

工具函数定义

首先定义 promise 的状态常量和和一些工具函数

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'


// 工具函数,判断是否为函数
const isFunction = fn => Object.prototype.toString.call(fn) === '[object Function]'
// 工具函数,判断状态是否为pending
const isPending = status => status === PENDING
// 工具函数,判断是否是promise实例
const isPromise = target => target instanceof Promise

经过上面我们知道,Promise 类接收一个函数作为参数,这个函数又有两个参数 resolve,reject,这两个参数也是方法。

Promise类创建

// 创建Promise类
class Promise  {
  constractor(executor){


    // 要求Promise的参数必须为函数,如果不是函数就抛出错误
    if(!isFunction(executor)){
      throw new Error('Promise must accept a function as a parameter')
    }


    this._status = PENDING // 初始化状态值为pending
    this._value = undefined // 给value设置默认值
    this._reason = undefined  // 给reason设置默认值
       // 执行executor方法,把参数resole,reject传递进来
            try {
                executor(this._resolve.bind(this), this._reject.bind(this))
            }        
            catch (err) { // executor执行出现异常调用rejected方法
                this._reject(err)
            }
  },


  // 定义executor的参数resolve的执行函数
  _resolve(value){
    const {_status: status} = this
    if (isPending(status)) { // 状态只能由pending变成其他两种状态,且只能改变一次
      this._value = value // 将resove方法传递进来的参数保存下来
      this._status = FULFILLED // 状态更新为fufilled
    }
  },


  // 定义executor的参数reject的执行函数
  _reject(reason){
    const {_status: status} = this
    if(isPending(status)){ // 状态只能由pending变成其他两种状态,且只能改变一次
      this._reason = reason
      this._status = REJECTED


    }
  }
}


then方法实现

在实现 then 方法之前,建议读者再仔细阅读上面 Promise/A+ 规范的 then 部分,有助于更好的理解这部分代码。

then 方法接受两个参数,onFulfilled 和 onRejected。并且 then 方法必须返回一个 promise 对象,可以被同一个 promise 对象调用多次,当 promise 成功状态时,所有 onFulfilled 需按照其注册顺序依次回调 当 promise 失败状态时,所有 onRejected 需按照其注册顺序依次回调。

首先在 constractor 增加两个数组,fulfilledQueues,rejectedQueues,用来维护每次 then 注册时的回调函数。

this._fulfilledQueues = [] // 添加成功回调函数队列
this._rejectedQueues = [] // 添加失败回调函数队列
// then 方法实现
then(onFulfilled, onRejected) {
    // 定义then方法返回的promise
    return new Promise((resolve, reject) => {
        // 成功时执行的函数
        let fulfilledHandle = (value) => {
        try {
            // 如果onFulfilled不是函数,忽略掉
            if (isFunction(onFulfilled)) {
                const result = onFulfilled(value)
                // 如果当前回调函数返回值为promise实例,需要等待其状态变化后再执行下一个回调
                if (isPromise(result)) {
                    result.then(resolve, reject)
                } else {
                // 如果当前回调函数返回值不是promise实例,直接执行下一个then的回调,并将结果作为参数
                    resolve(result)
                }
            }


        } catch (err) {
            // 如果新的promise执行出错,新的promise状态直接变为rejected,调用reject函数,并将error作为参数
              reject(err)
            }


        }


        // 失败时执行的函数,实现上同成功时执行的函数
        let rejectedHandle = (reason) => {
            try {
              // 如果onFulfilled不是函数,忽略掉
                if (isFunction(onRejected)) {
                  const result = onRejected(reason)
                    if (isPromise(result)) {
                        result.then(resolve, reject)
                    } else {
                        reject(reason)
                    }
                }
            } catch (err) {
                reject(err)
            }
        }


    const { _value: value, _status: status, _reason: reason, _fulfilledQueues: fulfilledQueues, _rejectedQueues: rejectedQueues } = this


    switch (status) {


        // 状态为pending时,将then方法回调函数加入执行队列等待执行
        case PENDING:
        fulfilledQueues.push(fulfilledHandle)
        rejectedQueues.push(rejectedHandle)
        break


        // 状态为fulfilled时,直接执行onFulfilled方法
        case FULFILLED:
        fulfilledHandle(value)
        break


        // 状态为rejected时,直接执行onRejected方法
        case REJECTED:
        rejectedHandle(reason)
        break
    }
})
}

实现then的链式调用

为了实现 then 方法的链式调用,我们需要修改 constractor 下的 _resolve 和 _reject 方法,让他们依次执行成功/失败任务队列中的函数。

 _resolve(value) {
        const { _status: status } = this
      const resolveFun = () => {
      if (isPending(status)) { // 状态只能由pending变成其他两种状态,且只能改变一次
            this._value = value // 将resove方法传递进来的参数保存下来
            this._status = FULFILLED // 状态更新为fufilled
            let cb
            while (cb = this._fulfilledQueues.shift()) {
                cb(value)
            }
         }
      }
      // 通过setTimeout 0 模拟同步的promise情况
      setTimeout(resolveFun, 0)
    }


    _reject(reason) {
        const { _status: status } = this
        const rejectFun = () => {
        if (isPending(status)) { // 状态只能由pending变成其他两种状态,且只能改变一次
            this._reason = reason
            this._status = REJECTED
            let cb
            while (cb = this._rejectedQueues.shift()) {
                cb(reason)
            }
        }
        }
        setTimeout(rejectFun, 0)
    }



这样我们就实现了一个符合 promsie/A+ 规范的 Promise。

下面再来实现es6的 Promise 的一些方法

finally方法

finally(callback){
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
}

Promise.resolve和Promise.reject

static resolve (value) {
  if (value instanceof Promise) return value
  return new Promise(resolve => resolve(value))
}


static reject (value) {
  return new Promise((resolve ,reject) => reject(value))
}

Promise.all

Promise.all 是 Promise 类的静态方法。

static all(list){
  return new Promise((resolve, reject) => {
    let count = 0 // promise状态结束的个数,初始化为0
    let resValues = [] // 状态转为fufilled的value值
    const listLength = list.length
    for (let i = 0; i < list.length; i++) {
      // 如果不是不是Promise,调用Promise.resolve方法
      const p = isPromise(list[i]) ? list[i] : this.resolve(list[i])
      p.then((res)=>{
        resValues.push(res)
        count++
        // 所有状态都变为fufilled,直接resolve,返回resValues
        if(count === listLength){
          resolve(resValues)
        }
      }, (e)=> {
        // 如果有一个reject,直接reject
        reject(e)
      })
    }
  })
}



Promsie.race

static race(list){
  return new Promise((resolve, reject) => {
      for(let i = 0; i < list.length; i++){
        const p = isPromise(list[i]) ? list[i] : this.resolve(list[i])
        // 只要有一个状态转变,新的promsie状态就转变
        p.then((res) => {
          resolve(res)
        }, (e) => {
          reject(e)
        })
      }
  })
}

下面是刚刚实现的 promsie 的完成代码:

// 定义promise的状态常量 
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'


// 工具函数,判断是否为函数
const isFunction = fn => Object.prototype.toString.call(fn) === '[object Function]'
// 工具函数,判断状态是否为pending
const isPending = status => status === PENDING
// 工具函数,判断是否是promise实例
const isPromise = target => target instanceof Promise




// 已经知道,promise 类接收一个函数作为参数,这个函数又有两个参数resolve,reject,这两个参数也是方法。
class Promise {
    constructor(executor) {


        // 要求Promise的参数必须为函数,如果不是函数就抛出错误
        if (!isFunction(executor)) {
            throw new Error('Promise must accept a function as a parameter')
        }


        this._status = PENDING // 初始化状态值为pending
        this._value = undefined // 给value设置默认值
        this._reason = undefined  // 给reason设置默认值
        this._fulfilledQueues = [] // 添加成功回调函数队列
        this._rejectedQueues = [] // 添加失败回调函数队列


        // 执行executor方法,把参数resole,reject传递进来
        try {
            executor(this._resolve.bind(this), this._reject.bind(this))
        }        catch (err) { // executor执行出现异常调用rejected方法
            this._reject(err)
        }
    }


    // 为了实现 then 方法的链式调用,我们需要修改constractor下的 _resolve 和 _reject 方法,让他们依次执行成功/失败任务队列中的函数。
    _resolve(value) {
        const { _status: status } = this
      const resolveFun = () => {
      if (isPending(status)) { // 状态只能由pending变成其他两种状态,且只能改变一次
            this._value = value // 将resove方法传递进来的参数保存下来
            this._status = FULFILLED // 状态更新为fufilled
            let cb
            while (cb = this._fulfilledQueues.shift()) {
                cb(value)
            }
         }
      }
      // 通过setTimeout 0 模拟同步的promise情况
      setTimeout(resolveFun, 0)


    }


    _reject(reason) {
        const { _status: status } = this
        const rejectFun = () => {
        if (isPending(status)) { // 状态只能由pending变成其他两种状态,且只能改变一次
            this._reason = reason
            this._status = REJECTED
            let cb
            while (cb = this._rejectedQueues.shift()) {
                cb(reason)
            }
        }
        }
        setTimeout(rejectFun, 0)


    }


    // then 方法实现
    then(onFulfilled, onRejected) {
        // 定义then方法返回的promise
        return new Promise((resolve, reject) => {
            // 成功时执行的函数
            let fulfilledHandle = (value) => {
            try {
                // 如果onFulfilled不是函数,直接忽略
                if (isFunction(onFulfilled)) {
                    const result = onFulfilled(value)
                    // 如果当前回调函数返回值为promise实例,需要等待其状态变化后再执行下一个回调
                    if (isPromise(result)) {
                        result.then(resolve, reject)
                    } else {
                    // 如果当前回调函数返回值不是promise实例,直接执行下一个then的回调,并将结果作为参数
                        resolve(result)
                    }
                }


            } catch (err) {
                // 如果新的promise执行出错,新的promise状态直接变为rejected,调用reject函数,并将error作为参数
                  reject(err)
                }


            }


            // 失败时执行的函数,实现上同成功时执行的函数
            let rejectedHandle = (reason) => {
                try {
                  // 如果onRejected不是函数,直接忽略
                    if (isFunction(onFulfilled)) {
                      const result = onRejected(reason)
                        if (isPromise(result)) {
                            result.then(resolve, reject)
                        } else {
                            reject(reason)
                        }
                    }
                } catch (err) {
                    reject(err)
                }
            }


        const { _value: value, _status: status, _reason: reason, _fulfilledQueues: fulfilledQueues, _rejectedQueues: rejectedQueues } = this


        switch (status) {


            // 状态为pending时,将then方法回调函数加入执行队列等待执行
            case PENDING:
            fulfilledQueues.push(fulfilledHandle)
            rejectedQueues.push(rejectedHandle)
            break


            // 状态为fulfilled时,直接执行onFulfilled方法
            case FULFILLED:
            fulfilledHandle(value)
            break


            // 状态为rejected时,直接执行onRejected方法
            case REJECTED:
            rejectedHandle(reason)
            break
        }


    })


    }


    finially(callback){
      let P = this.constructor;
    return this.then(
      value  => P.resolve(callback()).then(() => value),
      reason => P.resolve(callback()).then(() => { throw reason })
    );


    }


    static resolve (value) {
    if (value instanceof Promise) return value
    return new Promise(resolve => resolve(value))
  }


  static reject (value) {
    return new Promise((resolve ,reject) => reject(value))
  }


  static all(list){
      return new Promise((resolve, reject) => {
        let count = 0 // promise状态结束的个数,初始化为0
        let resValues = [] // 状态转为fufilled的value值
        const listLength = list.length
        for (let i = 0; i < list.length; i++) {
          // 如果不是不是Promise,调用Promise.resolve方法
          const p = isPromise(list[i]) ? list[i] : this.resolve(list[i])
          p.then((res)=>{
            resValues.push(res)
            count++
            // 所有状态都变为fufilled,直接resolve,返回resValues
            if(count === listLength){
              resolve(resValues)
            }
          }, (e)=> {
            // 如果有一个reject,直接reject
            reject(e)
          })
        }
      })
    }


    static race(list){
      return new Promise((resolve, reject) => {
          for(let i = 0; i < list.length; i++){
            const p = isPromise(list[i]) ? list[i] : this.resolve(list[i])
            // 只要有一个状态转变,新的promsie状态就转变
            p.then((res) => {
              resolve(res)
            }, (e) => {
              reject(e)
            })
          }
      })
    }
}

04 

总结

对比回调函数而言,Promise 可以通过链式写法,将异步操作以同步操作的流程表达出来。

Promise 的一个更大的特点是,它为所有的异步操作提供了一个统一的 API,这让我们可以更统一的处理异步操作。

然而 Promsie 的本质还是回调函数,只是在写法上进行了改进,在 then 中执行,使其看起来更清楚而已。带来的坏处也很明显,原有的代码都要使用promsie包装一层,一眼看去都是then,使原有的语义看起来不是十分清楚。

那么除了 Promise 有什么更好的写法吗?优缺点又是什么呢,欢迎关注重学js异步系列文章。

参考

https://segmentfault.com/a/1190000004325757

https://promisesaplus.com/

https://segmentfault.com/a/1190000002452115

https://es6.ruanyifeng.com/#docs/promise



更多文章请点击“阅读原文”

喜欢本文点个“在看”哟!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值