JavaScript面试必备技巧之手写一个Promise

 更新时间:2023年02月08日 15:06:59   作者:mick  
很多同学在面试的时候都会被要求手写一个Promise,那么今天我总结了一些手写Promise的方法,可以跟着我的思路一起来实现一个Promise,让我们的面试更有把握

很多同学在面试的时候都会被要求手写一个Promise,那么今天我总结了一些手写Promise的方法,可以跟着我的思路一起来实现一个Promise,让我们的面试更有把握。同时我们也会实现一下Promsie常见的方法比如:all、race、allSettled、any。

基本实现

首先我们可以用类来实现Promise,而且Promise有三种状态:pending、fulfilled、rejected。初始状态为pending。还需要对Promise的终值进行初始化。Promise还有两个方法resolve和reject。

Promise有四个特点:

  • 执行了resolve,Promise状态就会变成fulfilled
  • 执行了reject,Promise状态就会变成rejected
  • Promise状态不可逆,第一次成功就永久为fulfilled,第一次失败就永久为rejected
  • Promise中有throw的话,就相当于执行了rejected

下面我就来简单的实现一下吧

实现resolve和reject

class MyPromise {
  constructor(executor) {
    // 初始化值
    this.initValue()
    // 初始化this指向
    this.initBind()

    try {
      executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }

  initValue() {
    // 初始化值
    this.promiseResult = null
    this.promiseState = "pending" // 初始状态
  }

  initBind() {
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
  }

  resolve(val) {
    this.promiseState = "fulfilled"
    this.promiseResult = val
  }

  reject(reason) {
    this.promiseState = "rejected"
    this.promiseResult = reason
  }
}

测试一下吧

const test1 = new MyPromise((resolve, reject) => {
  resolve("success")
})

console.log(test1) 
// MyPromise{ promiseResult: 'success', promiseState: 'fulfilled' }

const test2 = new MyPromise((resolve, reject) => {
  reject("fail")
})

console.log(test2)
// MyPromise{ promiseResult: 'fail', promiseState: 'rejected' }

const test3 = new MyPromise((resolve, reject) => {
  throw "fail"
})
console.log(test3)
// MyPromise{ promiseResult: 'fail', promiseState: 'rejected' }

这里重点说一下initBind中为什么要给resolve和reject绑定this。我们可以看到在resolve和reject中使用了this.promiseStatethis.promiseResult。我们需要把它的this绑定到实例才对。

状态不可变

如果我们执行下面的代码

const test = new MyPromise((resolve, reject) => {
  resolve("success")
  reject("fail")
})
console.log(test)
// MyPromise{ promiseResult: 'fail', promiseState: 'rejected' }

这就不符合我们的预期了,因为我们需要的是状态不可变。所以我们将代码改造一下,这里只需要修改resolve和reject就可以了

resolve(val) {
    if (this.promiseState !== "pending") return
    this.promiseState = "fulfilled"
    this.promiseResult = val
}

reject(reason) {
    if (this.promiseState !== "pending") return
    this.promiseState = "rejected"
    this.promiseResult = reason
}

当执行resolve或reject的时候,发现状态不是pending就说明状态已经改变了,直接return即可。

then

我们首先看下原始的Promise的then实现的效果

// 直接输出success
const p1 = new Promise((resolve, reject) => {
  resolve("success")
}).then(
  (res) => console.log(res),
  (err) => console.log(err)
)

// 1s后输出fail
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("fail")
  }, 1000)
}).then(
  (res) => console.log(res),
  (err) => console.log(err)
)

// 链式调用 直接输出2000
const p3 = new Promise((resolve, reject) => {
  resolve(1000)
})
  .then(
    (res) => 2 * res,
    (err) => console.log(err)
  )
  .then(
    (res) => console.log(res),
    (err) => console.log(err)
  )

由此可以得出结论:

  • then 接受两个回调,一个是成功回调, 一个是失败的回调
  • 当Promise为fulfilled执行成功的回调,为rejected 执行失败的回调
  • 如果resolve或者reject在定时器里面执行,则定时器结束后再执行then
  • then 支持链式调用,下一次then执行受上一次then返回值的影响

then实现

由结论1和2可以实现

then(onFulfilled, onRejected) {
    // 首先要校验两个回调是否是函数
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val) => val
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason
          }

    if (this.promiseState === "fulfilled") {
      onFulfilled(this.promiseResult)
    } else if (this.promiseState === "rejected") {
      onRejected(this.promiseResult)
    }
}

但是我们如何保证回调是在定时器结束后执行呢?首先在定时器结束之前Promise的状态一直是pending的,回调结束之后我们才能知道是fulfilled或者是rejected,所以在执行then的时候,如果promiseState是pending我们就把回调收集起来,当回调结束之后再触发。那使用什么来保存这些回调的呢?这里建议使用数组,因为一个Promise实例可能会多次执行then,用数组一个个保存就可以了

initValue() {
    // 初始化值
    this.promiseResult = null
    this.promiseState = "pending" // 初始状态
    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []
}
resolve(val) {
    if (this.promiseState !== "pending") return
    this.promiseState = "fulfilled"
    this.promiseResult = val
    while (this.onFulfilledCallbacks.length) {
      this.onFulfilledCallbacks.shift()(this.promiseResult)
    }
}

reject(reason) {
    if (this.promiseState !== "pending") return
    this.promiseState = "rejected"
    this.promiseResult = reason
    while (this.onRejectedCallbacks.length) {
      this.onRejectedCallbacks.shift()(this.promiseResult)
    }
}
then(onFulfilled, onRejected) {
    // 首先要校验两个回调是否是函数
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val) => val
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason
          }

    if (this.promiseState === "fulfilled") {
      onFulfilled(this.promiseResult)
    } else if (this.promiseState === "rejected") {
      onRejected(this.promiseResult)
    } else if (this.promiseState === "pending") {
      this.onFulfilledCallbacks.push(onFulfilled.bind(this))
      this.onRejectedCallbacks.push(onRejected.bind(this))
    }
}

链式调用

我们再来重新看下链式调用的例子

// 链式调用 直接输出2000
const p3 = new Promise((resolve, reject) => {
  resolve(1000)
})
  .then(
    (res) => 2 * res,
    (err) => console.log(err)
  )
  .then(
    (res) => console.log(res),
    (err) => console.log(err)
  )

// 链式调用 输出3000
const p4 = new Promise((resolve, reject) => {
  resolve(1000)
})
  .then(
    (res) => new Promise((resolve, reject) => resolve(3 * res)),
    (err) => console.log(err)
  )
  .then(
    (res) => console.log(res),
    (err) => console.log(err)
  )

由此可得:

  • then方法本身会返回一个新的Promise对象
  • 如果返回值是promise对象,返回值为成功,新promise就是成功
  • 如果返回值是promise对象,返回值为失败,新promise就是失败
  • 如果返回值是非promise对象,新promise对象就是成功,值为此返回值

这里我们对then改造了一下

then(onFulfilled, onRejected) {
    // 首先要校验两个回调是否是函数
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val) => val
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason
          }

    var thenPromise = new MyPromise((resolve, reject) => {
      const resolvePromise = (cb) => {
        try {
          const x = cb(this.promiseResult)

          if (x === thenPromise && x) {
            throw new Error("不能返回自身")
          }
          if (x instanceof MyPromise) {
            // 如果是promise 返回值为成功 新promise 就是成功
            // 如果是promise 返回值失败 新promise  就是失败
            x.then(resolve, reject)
          } else {
            // 如果不是promise 直接返回成功
            resolve(x)
          }
        } catch (error) {
          reject(error)
          throw new Error(error)
        }
      }

      if (this.promiseState === "fulfilled") {
        resolvePromise(onFulfilled)
      } else if (this.promiseState === "rejected") {
        resolvePromise(onRejected)
      } else if (this.promiseState === "pending") {
        this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled))
        this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected))
      }
    })

    return thenPromise
  }

then会返回一个新的Promise,在这个新的promise中定义了方法resolvePromise ,接收一个cb回调函数,这个cb就是传入的onFulfilled或者onRejected。如果这两个回调返回的不是promise,那结果直接resolve出去。如果是promise,那么就需要根据它的状态来决定下一步操作。那它到底是成功还是失败的呢,只有它的then知道。然后把resolve和reject当做回调传给then,如果x返回的是成功的Promise,则会执行resolve, 如果x返回的是失败的promise,则会执行reject。这样链式调用的then才会知道执行哪一个回调。

执行顺序

const p = new Promise((resolve, reject) => {
  resolve(1)
}).then(
  (res) => console.log(res),
  (err) => console.log(err)
)

console.log(2)

为了实现类似的功能,使用setTimeout代替

看下完整代码

class MyPromise {
  constructor(executor) {
    // 初始化值
    this.initValue()
    // 初始化this指向
    this.initBind()

    try {
      executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }

  initValue() {
    // 初始化值
    this.promiseResult = null
    this.promiseState = "pending" // 初始状态
    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []
  }

  initBind() {
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
  }

  resolve(val) {
    if (this.promiseState !== "pending") return
    this.promiseState = "fulfilled"
    this.promiseResult = val
    while (this.onFulfilledCallbacks.length) {
      this.onFulfilledCallbacks.shift()(this.promiseResult)
    }
  }

  reject(reason) {
    if (this.promiseState !== "pending") return
    this.promiseState = "rejected"
    this.promiseResult = reason
    while (this.onRejectedCallbacks.length) {
      this.onRejectedCallbacks.shift()(this.promiseResult)
    }
  }

  then(onFulfilled, onRejected) {
    // 首先要校验两个回调是否是函数
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val) => val
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason
          }

    var thenPromise = new MyPromise((resolve, reject) => {
      const resolvePromise = (cb) => {
        setTimeout(() => {
          try {
            const x = cb(this.promiseResult)

            if (x === thenPromise && x) {
              throw new Error("不能返回自身")
            }

            if (x instanceof MyPromise) {
              // 如果是promise 返回值为成功 新promise 就是成功
              // 如果是promise 返回值失败 新promise  就是失败
              x.then(resolve, reject)
            } else {
              // 如果不是promise 直接返回成功
              resolve(x)
            }
          } catch (error) {
            reject(error)
            throw new Error(error)
          }
        })
      }

      if (this.promiseState === "fulfilled") {
        resolvePromise(onFulfilled)
      } else if (this.promiseState === "rejected") {
        resolvePromise(onRejected)
      } else if (this.promiseState === "pending") {
        this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled))
        this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected))
      }
    })

    return thenPromise
  }
}

其他方法

all

  • 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
  • 如果所有Promise都成功,则返回成功结果数组
  • 如果有一个Promise失败,则返回这个失败的结果
static all(promiseList) {
    const result = []
    const count = 0

    return new MyPromise((resolve, reject) => {
      const addData = function (index, value) {
        result[index] = value
        count++

        if (count === promiseList.length) resolve(result)
      }

      promiseList.forEach((promise, index) => {
        if (promise instanceof MyPromise) {
          promise.then(
            (res) => {
              addData(index, res)
            },
            (err) => {
              reject(err)
            }
          )
        } else {
          addData(index, promise)
        }
      })
    })
}

race

  • 接收一个Promise数组,数组中有非Promise项,则此项当做成功
  • 哪个Promise最快得到结果,就返回那个结果,无论成功失败
race(promiseList) {
    return new MyPromise((resolve, reject) => {
      promiseList.forEach((promise) => {
        if (promise instanceof MyPromise) {
          promise.then(
            (res) => resolve(res),
            (err) => reject(err)
          )
        } else {
          resolve(promise)
        }
      })
    })
}

allSettled

  • 接收一个Promise数组,数组中有非Promise项,则此项当做成功
  • 把每个Promise的结果,集合成数组后返回
allSettled(promiseList) {
    const result = []
    let count = 0
    return new MyPromise((resolve, reject) => {
      const addData = function (status, value, i) {
        result[i] = {
          status,
          value
        }

        count++
        if (count === promiseList.length) {
          resolve(result)
        }
      }

      promiseList.forEach((promise, index) => {
        if (promise instanceof MyPromise) {
          promise.then(
            (res) => {
              addData("fulfilled", res, index)
            },
            (err) => {
              addData("reject", err, index)
            }
          )
        } else {
          resolve(promise)
        }
      })
    })
}

any

与all相反

  • 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
  • 如果有一个Promise成功,则返回这个成功结果
  • 如果所有Promise都失败,则报错
static any(promiseList) {
    return new MyPromise((resolve, reject) => {
      let count = 0
      promiseList.forEach((promise) => {
        if (promise instanceof MyPromise) {
          promise.then(
            (res) => {
              resolve(res)
            },
            (err) => {
              count++
              if (count === promiseList.length) {
                reject("error")
              }
            }
          )
        } else {
          resolve(promise)
        }
      })
    })
  }

到此这篇关于JavaScript面试必备技巧之手写一个Promise的文章就介绍到这了,更多相关JavaScript手写Promise内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JavaScript实现简单的日历效果

    JavaScript实现简单的日历效果

    本文给大家分享的是一个简单的JavaScript制作的日历模板,小伙伴们可以根据自己的需求,继续补充,希望大家能够喜欢
    2016-09-09
  • 微信小程序多列表渲染数据开关互不影响的实现

    微信小程序多列表渲染数据开关互不影响的实现

    这篇文章主要介绍了微信小程序多列表渲染数据开关互不影响的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • 如何用JavaScipt测网速

    如何用JavaScipt测网速

    这篇文章主要介绍了如何用JavaScipt测网速,对测网速感兴趣的同学,可以参考下
    2021-05-05
  • js实现动态添加、删除行、onkeyup表格求和示例

    js实现动态添加、删除行、onkeyup表格求和示例

    动态添加、删除行想必大家并不陌生,下面为大家介绍通过js是如何实现的,有此需求的朋友可不要错过了哈
    2013-08-08
  • 小程序绑定用户方案优化小结

    小程序绑定用户方案优化小结

    这篇文章主要介绍了小程序绑定用户方案优化小结,该类小程序在使用之前就需要绑定用户信息。常见于线下门店类功能性小程序。具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-05-05
  • JavaScript使用atan2来绘制箭头和曲线的实例

    JavaScript使用atan2来绘制箭头和曲线的实例

    下面小编就为大家带来一篇JavaScript使用atan2来绘制箭头和曲线的实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • javascript 模拟JQuery的Ready方法实现并出现的问题

    javascript 模拟JQuery的Ready方法实现并出现的问题

    今天在阅读网上一些模拟Jq的ready方法时,发现一些小细节,就是网上的ready事件大部分都是在onload事件执行后加载,而jquery确能在onload加载前。
    2009-12-12
  • JavaScript实现兼容IE6的收起折叠与展开效果实例

    JavaScript实现兼容IE6的收起折叠与展开效果实例

    这篇文章主要介绍了JavaScript实现兼容IE6的收起折叠与展开效果,结合具体实例形式分析了javascript事件响应及针对页面元素属性的动态操作相关实现技巧,需要的朋友可以参考下
    2017-09-09
  • JavaScript表单验证完美代码

    JavaScript表单验证完美代码

    用原生JS写一个简单的表单验证功能,代码分为html部分和js部分,代码简单易懂,非常不错,具有参考借鉴价值,需要的朋友参考下
    2017-03-03
  • Bootstrap每天必学之弹出框(Popover)插件

    Bootstrap每天必学之弹出框(Popover)插件

    Bootstrap每天必学之弹出框(Popover)插件,弹出框的内容完全可使用 Bootstrap 数据 API(Bootstrap Data API)来填充,如何实现请参考本文
    2016-04-04

最新评论