前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解 Promise 之手把手教你写一版

深入理解 Promise 之手把手教你写一版

作者头像
用户1097444
发布2022-06-29 14:10:16
5010
发布2022-06-29 14:10:16
举报
文章被收录于专栏:腾讯IMWeb前端团队

什么是 Promise?

  • 语法上:Promise 是一个构造函数,返回一个带有状态的对象
  • 功能上:Promise 用于解决异步函数并根据结果做出不同的应对
  • 规范上:Promise 是一个拥有 then 方法的对象(在 JS 里函数也是对象)

为什么要用 Promise?

前端最令人头疼的事情之一就是处理异步请求:

代码语言:javascript
复制
function load() {
    $.ajax({
        url: 'xxx.com',
        data: 'jsonp',
        success: function(res) {
            init(res, function(res) {
                render(res, function(res) {
                    // 千层饼
                });
            });
        }
    }
}

代码层级多,可读性差且难以维护,形成回调地狱。

有了 Promise,我们可以用同步操作的流程写异步操作,解决了层层嵌套的回调函数的困扰:

代码语言:javascript
复制
new Promise(
  function (resolve, reject) {
    // 一段耗时的异步操作
    resolve('成功') 
    or
    reject('失败') 
  }
 ).then(
   res => {console.log(res)},  // 成功
 ).catch(
   err => {console.log(err)}   // 失败
 )

当然,Promise 也有缺点

  • 无法取消 Promise,一旦新建就会立即执行,无法中途取消
  • 如果不设置回调函数,无法抛出 Promise 内部错误到外部
  • 当处于 Pending 状态时,无法得知目前运行的情况,是刚开始还是快结束

事不宜迟,我们马上开始!

Promise 的状态

Promise 有以下 3 种状态:

代码语言:javascript
复制
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

状态只能由 pending 向 fulfilled 或 rejected 转变,且只有在执行环境堆栈仅包含平台代码时转变一次,称为状态凝固,并保存一个参数表明结果。

代码语言:javascript
复制
this.value = value   // fulfilled状态,保存终值
this.reason = reason // rejected状态,保存据因

Promise 构造函数

promise 构造函数接受一个函数作为参数,我们称该函数参数为 executor,待 promise 执行时,会向 executor 传入两个函数参数,分别为 resolve 和 reject,它们只做 3 件事:

  • 改变 promise 状态
  • 保存 value/reason 结果
  • 执行 onFulfilled/onRejected 回调函数

其中第三条即为 then 方法中配置的回调函数,这里先不做多讨论,先看前两条,只需要两行代码即可:

代码语言:javascript
复制
this.state = state
this.value = value / this.reason = reason

我们先手撸一个简单的构造函数:

代码语言:javascript
复制
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise{
  constructor(executor) {
    this.state = PENDING  // prpmise具有初始状态
    this.value = null// 用于保存终值
    this.reason = null// 用于保存拒因

    this.onFulfilledCallbacks = [] // 成功回调队列
    this.onRejectedCallbacks = []  // 失败回调队列
    // 定义 resolve 函数
    // 这里使用箭头函数以解决 this 的指向,不了解的朋友可以先看阮大大的ES6文章
    const resolve = value => {
    // 保证状态只能改变一次
        if (this.state === PENDING) {
          this.state = FULFILLED
          this.value = value
        }
    }
    // 同上
    const reject = reason => {
        if (this.state === PENDING) {
          this.state = REJECTED
          this.reason = reason
        }
    }
    // executor 可能会出现异常,需要捕获并调用reject函数表示执行失败
    try {
        // 传入两个函数参数
        executor(resolve, reject)
    } catch (e) {
        reject(e)
    }
  }
}

看上去还不错,大概的流程已经完成了。还记得之前说过,状态的改变是处于主线程空闲时,这里使用 setTimeout 来模拟,以及 resolve/reject 还剩下第 3 件事,现在就让我们一起完善它吧:

代码语言:javascript
复制
const resolve = value => {
     // setTimeout 模拟
     // 注意 即便是判断状态是否为 pending 也应该要在主线程空闲时执行
     setTimeout(() => {
        if (this.state === PENDING) {
          this.state = FULFILLED
          this.value = value
          // 若是使用 forEach 回调函数有可能不按顺序执行
          this.onFulfilledCallbacks.map(cb => cb(this.value))
        } 
     })
 }
 // reject同上

好啦,一个完整的构造函数就写完了,是不是觉得很轻松,Promise 不过如此。

接下来是重头戏 then 方法,then 接受两个函数参数,分别为 onFulfilled/onRejected,用来配置 promise 状态改变后的回调函数。

其有两个重点:

  1. 返回一个 promise2,以实现链式调用
  • 其中 promise2 的状态必须要凝固
  • 通过 resolvePromise 函数以及 onFulfilled/onRejected 的返回值来实现 promise2 的状态凝固
  1. 监听或执行对应的 onFulfilled/onRejected 回调函数
  • 若是执行则需放入 event-loop
  • 监听只需推入回调函数数组中

上述的 resolvePromise 我们先不理会,只要知道它是用来决定 promise2 的状态即可。

首先,then 需要返回一个 promise2

代码语言:javascript
复制
then(onFulfilled, onRejected) {
        let promise2
        return (promise2 = new MyPromise((resolve, reject) => {
            
        })
    }

其次,then 方法的目的是配置或执行对应的 onFulfilled/onRejected 回调函数:

代码语言:javascript
复制
then(onFulfilled, onRejected) {
        let promise2
        return (promise2 = new MyPromise((resolve, reject) => {
            // 将回调函数配置好并推入对应的 callbacks 数组中
            this.onFulfilledCallbacks.push(value => {
                // 配置第一步:执行 callback 并保存返回值 x
                let x = onFulfilled(value);
                // 配置第二步:通过 resolvePromise 决定 promise2 状态
                resolvePromise(promise2, x, resolve, reject)
            })
            // onRejected 同上
            this.onRejectedCallbacks.push(reason => {
                let x = onRejected(reason)
                resolvePromise(promise2, x, resolve, reject)
            })
        })
    }

在这里可以大概了解 resolvePromise 是如何改变 promise2 状态的,它接受 promise2 的 resolve/reject,由于箭头函数的原因,resolve/reject 的 this 指向依旧指向 promise2,从而可以通过 resolvePromise 来改变状态。

万一 onFulfilled/onRejected 出错怎么办?我们需要将它捕获并将 promise2 的状态改为 rejected,我们将代码再做修改:

代码语言:javascript
复制
then(onFulfilled, onRejected) {
        let promise2
        return (promise2 = new MyPromise((resolve, reject) => {
            // 将回调函数配置好并推入对应的 callbacks 数组中
            this.onFulfilledCallbacks.push(value => {
                try {
                    let x = onFulfilled(value);
                    resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            
            })
            // onRejected 同上
            this.onRejectedCallbacks.push(reason => {
                try {
                    let x = onRejected(reason)
                    resolvePromise(promise2, x, resolve, reject) 
                } catch (e) {
                    reject(e)
                }
            })
        })
      }
   }

如果调用 then 方法的是已经状态凝固的 promise 呢,也要推入 callbacks 数组吗?答案当然不是,而是直接将配置好的 onFulfilled/onRejected 扔入 event-loop 中,就不劳烦 resolve/reject 了:

代码语言:javascript
复制
then(onFulfilled, onRejected){
    // fulfilled 状态,将配置好的回调函数扔入 event-loop
    if (this.state === FULFILLED) {
      return (promise2 = new MyPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }
    // rejected 状态同上
    if (this.state === REJECTED) {
      return (promise2 = new MyPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }
    // pending 状态则交由 resolve/reject 来决定
    if (this.state === PENDING) {
      return (promise2 = new MyPromise((resolve, reject) => {
        this.onFulfilledCallbacks.push(value => {
          try {
            let x = onFulfilled(value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })

        this.onRejectedCallbacks.push(reason => {
          try {
            let x = onRejected(reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }
  }
}

看上去完美了,不过还差一件小事,假如 promise 使用者不按套路出牌,传入的 onFulfilled/onRejected 不是一个函数怎么办?这里我们就直接将之作为返回值直接返回:

代码语言:javascript
复制
then(onFulfilled, onRejected){
    let promise2
    // 确保 onFulfilled/onRejected 为函数
    // 若非函数,则转换为函数并且返回值为自身
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {
      throw reason
    }

    if (this.state === FULFILLED) {
      return (promise2 = new MyPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }

    if (this.state === REJECTED) {
      return (promise2 = new MyPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }

    if (this.state === PENDING) {
      return (promise2 = new MyPromise((resolve, reject) => {
        this.onFulfilledCallbacks.push(value => {
          try {
            let x = onFulfilled(value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })

        this.onRejectedCallbacks.push(reason => {
          try {
            let x = onRejected(reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }
  }
}

大功告成!

最后只剩下一个 resolvePromise 方法,先介绍一下它的功能:根据回调函数的返回值 x 决定 promise2 的最终状态:

  • 如果 x 为 thenable 对象,即带 then 方法的对象
    • 有,因其不一定符合 promise 的标准,我们做多一些准备
    • 无,当作普通值执行
    • 使用 called 变量使得其状态改变只能发生一次
    • 监听异常
    • 递归调用 resolvePromise 以防止出现套娃
    • 如果 x 为 promise,则递归调用,直到返回值为普通值为止
    • 如果 x 为函数或对象,判断其有无 then 方法
  • x 为普通值
    • 直接返回

让我们来一步一步刨析它吧:

代码语言:javascript
复制
function resolvePromise(promise2, x, resolve, reject){
    // 先从 x 为 thenable 对象开始
    // 如果 x === promise2 需要抛出循环引用错误,否则会死循环
    if (x === promise2) {
        reject(newTypeError('循环引用'))
    }
    // 如果 x 就是 promise
    // 根据 x 的状态来决定 promise2 的状态
    if (x instanceof MyPromise) {
        // x 状态为 PENDING 时
        // 当 x 被 resolve 会调用新的 resolvePromise
        // 因为怕 resolve 保存的终值还是 promise 继续套娃
        // 所以一定要递归调用 resolvePromise 保证最终返回的一定是普通值
        // 失败直接调用 reject 即可
        if (x.state === PENDING) {
            x.then(
                y => {
                    resolvePromise(promise2, y, resolve, reject)
                },
                r => {
                    reject(r)
                }
            )
        } else {
            // x 状态凝固,直接配置即可
            // 不过这里有个疑问
            // 如果之前 resolve 保存的终值还是 promise 呢
            // 该怎样预防这一问题,后续将会讲到
            x.then(resolve, reject)
        }
    }
}

现在把应对 x 的值为 promise 的代码书写完毕,但这还不够,我们要面对的不只是 promise,而是一个 thenable 对象,所以还要继续判断:

代码语言:javascript
复制
function resolvePromise(promise2, x, resolve, reject) {
  if (x === promise2) {
    reject(newTypeError('循环引用'))
  }
  if (x instanceof MyPromise) {
     // 前面的代码不再赘述
  } elseif (x && (typeof x === 'function' || typeof x === 'object')) {
      // 因为不一定是规范的 promise 对象
      // 我们需要保证状态的改变只发生一次
      // 加入一个 called 变量来加锁
      let called = false
      // 还是因为不一定是规范的 promise 对象
      // 需要保证运行时异常能够被捕获
      try {
          // 注意,前面不加 try/catch
          // 仅仅下面这一行代码也有可能会报错而无法被捕获
          let then = x.then
          // 假如 x.then 存在并为函数
          if (typeof then === 'function') {
              // 使用 call 方法保证 then 的调用对象为 x
              then.call{
                  x,
                  y => {
                      // 假如状态凝固便不再执行
                      if (called) return
                      called = true
                      // 防止出现 resolve 保存 promise 的情况
                      resolvePromise(promise2, y, resolve, reject)
                  },
                  r => {
                      // 同上
                      if (called) return
                      called = true
                      reject(r)
                  }
              } 
         } else {
            // 如果 x.then 不是函数
            // 即为普通值,直接 resolve 就好
            resolve(x)
        }
       } catch (e) {
          // 若调用一个不正规的 thenalbe 对象出错
          // 抛出异常
          // 这里要注意,这里出现错误很有可能是执行了 x.then 方法,而之前也说过,其不一定正规,可能状态已经凝固,需要多加一重保险
          if (called) return
          called = true
          reject(e)
        }
     } else {
       // 不是 thenable 对象,那就是普通值
       // 直接 resolve
       resolve(x)   
    }
}

一套行云流水的代码写下来,我们的 promise 就完成了,不过还记得之前代码里留了个疑问吗?当 x 为 promise 且状态凝固时,如果确定它保存的终值的不是 promise 呢?其实只要最开始的 resolve 函数多加一重判断即可:

代码语言:javascript
复制
const resolve = value => {
      if (value instanceof MyPromise) {
        return value.then(resolve, reject)
      }

      setTimeout(() => {
        if (this.state === PENDING) {
          this.state = FULFILLED
          this.value = value
          this.onFulfilledCallbacks.map(cb => cb(this.value))
        }
      })
    }

再次防止套娃!

好啦,也许你会问,我怎么知道这个手写的 promise 就一定是正确的呢?接下来将一步步带你验证!

首先找到一个空文件夹,在命令行输入:

代码语言:javascript
复制
    npm init -y
    // 下载 promise 测试工具
    npm install promises-aplus-tests -D

新建 promise.js 文件,并将你实现的 promise 复制于此,并在下方加入一下代码:

代码语言:javascript
复制
MyPromise.deferred = function() {
  let defer = {}
  defer.promise = new MyPromise((resolve, reject) => {
    defer.resolve = resolve
    defer.reject = reject
  });
  return defer
}

module.exports = MyPromise

再修改 package.json 文件如下:

代码语言:javascript
复制
{
  "name": "promise",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "promises-aplus-tests ./promise.js"
  },
  "devDependencies": {
    "promises-aplus-tests": "^2.1.2"
  }
}

最后一步:

代码语言:javascript
复制
    npm run test

完成!

手写 Promise,你也可以!

最后附上完整实现代码

代码语言:javascript
复制
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  constructor(executor) {
    this.state = PENDING
    this.value = null
    this.reason = null

    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []

    const resolve = value => {
      if (value instanceof MyPromise) {
        return value.then(resolve, reject)
      }

      setTimeout(() => {
        if (this.state === PENDING) {
          this.state = FULFILLED
          this.value = value
          this.onFulfilledCallbacks.map(cb => cb(this.value))
        }
      })
    }

    const reject = reason => {
      setTimeout(() => {
        if (this.state === PENDING) {
          this.state = REJECTED
          this.reason = reason
          this.onRejectedCallbacks.map(cb => cb(this.reason))
        }
      })
    }

    try {
      executor(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }

  then(onFulfilled, onRejected) {
    let promise2

    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {
      throw reason
    }

    if (this.state === FULFILLED) {
      return (promise2 = new MyPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }

    if (this.state === REJECTED) {
      return (promise2 = new MyPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }

    if (this.state === PENDING) {
      return (promise2 = new MyPromise((resolve, reject) => {
        this.onFulfilledCallbacks.push(value => {
          try {
            let x = onFulfilled(value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })

        this.onRejectedCallbacks.push(reason => {
          try {
            let x = onRejected(reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }
  }
}
function resolvePromise(promise2, x, resolve, reject) {
  if (x === promise2) {
    reject(newTypeError('循环引用'))
  }

  if (x instanceof MyPromise) {
    if (x.state === PENDING) {
      x.then(
        y => {
          resolvePromise(promise2, y, resolve, reject)
        },
        r => {
          reject(r)
        }
      )
    } else {
      x.then(resolve, reject)
    }
  } elseif (x && (typeof x === 'function' || typeof x === 'object')) {
    let called = false
    try {
      let then = x.then
      if (typeof then === 'function') {
        then.call(
          x,
          y => {
            if (called) return
            called = true
            resolvePromise(promise2, y, resolve, reject)
          },
          r => {
            if (called) return
            called = true
            reject(r)
          }
        )
      } else {
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    resolve(x)
  }
}

MyPromise.deferred = function() {
  let defer = {}
  defer.promise = new MyPromise((resolve, reject) => {
    defer.resolve = resolve
    defer.reject = reject
  });
  return defer
};

module.exports = MyPromise;

紧追技术前沿,深挖专业领域

扫码关注我们吧!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯IMWeb前端团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是 Promise?
  • 为什么要用 Promise?
  • Promise 的状态
  • Promise 构造函数
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档