JavaScript 进阶营让前端飞

js常见面试题——详解Promise使用与原理及实现过程(附源码

2021-04-19  本文已影响0人  程序员阿野

一、什么是 Promise

promise 是目前 JS 异步编程的主流解决方案,遵循 Promises/A+ 方案。

二、Promise 原理简析

(1)promise 本身相当于一个状态机,拥有三种状态

(2)promise 对象原型上有一个 then 方法,then 方法会返回一个新的 promise 对象,并且将回调函数 return 的结果作为该 promise resolve 的结果,then 方法会在一个 promise 状态被扭转为 fulfilled 或 rejected 时被调用。then 方法的参数为两个函数,分别为 promise 对象的状态被扭转为 fulfilled 和 rejected 对应的回调函数

三、Promise 如何使用

构造一个 promise 对象,并将要执行的异步函数传入到 promise 的参数中执行,并且在异步执行结束后调用 resolve( ) 函数,就可以在 promise 的 then 方法中获取到异步函数的执行结果

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, 1000)
}).then(
  res => {},
  err => {}
)

同时在 Promise 还为我们实现了很多方便使用的方法:

resolve方法

Promise.resolve 返回一个 fulfilled 状态的 promise

const a = Promise.resolve(1)
a.then(
  res => {
    // res = 1
  },
  err => {}
)

all方法

Promise.all 接收一个 promise 对象数组作为参数,只有全部的 promise 都已经变为 fulfilled 状态后才会继续后面的处理。Promise.all 本身返回的也是一个 promise

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('promise1')
  }, 100)
})
const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('promise2')
  }, 100)
})
const promises = [promise1, promise2]

Promise.all(promises).then(
  res => {
    // promises 全部变为 fulfilled 状态的处理
  },
  err => {
    // promises 中有一个变为 rejected 状态的处理
  }
)

race方法

Promise.race 和 Promise.all 类似,只不过这个函数会在 promises 中第一个 promise 的状态扭转后就开始后面的处理(fulfilled、rejected 均可)

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('promise1')
  }, 100)
})
const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('promise2')
  }, 1000)
})
const promises = [promise1, promise2]

Promise.race(promises).then(
  res => {
    // 此时只有 promise1 resolve 了,promise2 仍处于 pending 状态
  },
  err => {}
)

四、配合 async await 使用

现在的开发场景中我们大多会用 async await 语法糖来等待一个 promise 的执行结果,使代码的可读性更高。async 本身是一个语法糖,将函数的返回值包在一个 promise 中返回。

// async 函数会返回一个 promise
const p = async function f() {
  return 'hello world'
}
p.then(res => console.log(res)) // hello world

五、开发技巧

在前端开发上 promise 大多被用来请求接口,Axios 库也是开发中使用最频繁的库,但是频繁的 try catch 扑捉错误会让代码嵌套很严重。考虑如下代码的优化方式

const getUserInfo = async function() {
  return new Promise((resolve, reject) => {
    // resolve() || reject()
  })
}
// 为了处理可能的抛错,不得不将 try catch 套在代码外边,
// 一旦嵌套变多,代码可读性就会急剧下降
try {
  const user = await getUserInfo()
} catch (e) {}

好的处理方法是在异步函数中就将错误 catch,然后正常返回,如下所示 👇

const getUserInfo = async function() {
  return new Promise((resolve, reject) => {
    // resolve() || reject()
  }).then(
    res => {
      return [res, null] // 处理成功的返回结果
    },
    err => {
      return [null, err] // 处理失败的返回结果
    }
  )
}

const [user, err] = await getUserInfo()
if (err) {
  // err 处理
}

// 这样的处理是不是清晰了很多呢

六、Promise 源码实现

知识的学习需要知其然且知其所以然,所以通过一点点实现的一个 promise 能够对 promise 有着更深刻的理解。

(1)首先按照最基本的 promise 调用方式实现一个简单的 promise (基于 ES6 规范编写),假设我们有如下调用方式

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 1000)
})
  .then(
    res => {
      console.log(res)
      return 2
    },
    err => {}
  )
  .then(
    res => {
      console.log(res)
    },
    err => {}
  )

我们首先要实现一个 Promise 的类,这个类的构造函数会传入一个函数作为参数,并且向该函数传入 resolve 和 reject 两个方法。
初始化 Promise 的状态为 pending。

class MyPromise {
  constructor(executor) {
    this.executor = executor
    this.value = null
    this.status = 'pending'

    const resolve = value => {
      if (this.status === 'pending') {
        this.value = value          // 调用 resolve 后记录 resolve 的值
        this.status = 'fulfilled'   // 调用 resolve 扭转 promise 状态
      }
    }

    const reject = value => {
      if (this.status === 'pending') {
        this.value = value          // 调用 reject 后记录 reject 的值
        this.status = 'rejected'    // 调用 reject 扭转 promise 状态
      }
    }

    this.executor(resolve, reject)
  }

(2)接下来要实现 promise 对象上的 then 方法,then 方法会传入两个函数作为参数,分别作为 promise 对象 resolve 和 reject 的处理函数。
这里要注意三点:

class MyPromise {
  constructor(executor) {
    this.executor = executor
    this.value = null
    this.status = 'pending'
    this.onFulfilledFunctions = [] // 存放这个 promise 注册的 then 函数中传的第一个函数参数
    this.onRejectedFunctions = [] // 存放这个 promise 注册的 then 函数中传的第二个函数参数
    const resolve = value => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'
        this.onFulfilledFunctions.forEach(onFulfilled => {
          onFulfilled() // 将 onFulfilledFunctions 中的函数拿出来执行
        })
      }
    }
    const reject = value => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'rejected'
        this.onRejectedFunctions.forEach(onRejected => {
          onRejected() // 将 onRejectedFunctions 中的函数拿出来执行
        })
      }
    }
    this.executor(resolve, reject)
  }

  then(onFulfilled, onRejected) {
    const self = this
    if (this.status === 'pending') {
      /**
       *  当 promise 的状态仍然处于 ‘pending’ 状态时,需要将注册 onFulfilled、onRejected 方法放到 promise 的 onFulfilledFunctions、onRejectedFunctions 备用
       */
      return new MyPromise((resolve, reject) => {
        this.onFulfilledFunctions.push(() => {
          const thenReturn = onFulfilled(self.value)
          resolve(thenReturn)
        })
        this.onRejectedFunctions.push(() => {
          const thenReturn = onRejected(self.value)
          resolve(thenReturn)
        })
      })
    } else if (this.status === 'fulfilled') {
      return new MyPromise((resolve, reject) => {
        const thenReturn = onFulfilled(self.value)
        resolve(thenReturn)
      })
    } else {
      return new MyPromise((resolve, reject) => {
        const thenReturn = onRejected(self.value)
        resolve(thenReturn)
      })
    }
  }
}

对于以上完成的 MyPromise 进行测试,测试代码如下

const p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 1000)
})

p.then(res => {
  console.log('first then', res)
  return res + 1
}).then(res => {
  console.log('first then', res)
})

p.then(res => {
  console.log(`second then`, res)
  return res + 1
}).then(res => {
  console.log(`second then`, res)
})

/**
 *  输出结果如下:
 *  first then 1
 *  first then 2
 *  second then 1
 *  second then 2
 */

(3)在 promise 相关的内容中,有一点常常被我们忽略,当 then 函数中返回的是一个 promise 应该如何处理?
考虑如下代码:

// 使用正确的 Promise
new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, 1000)
})
  .then(res => {
    console.log('外部 promise')
    return new Promise((resolve, reject) => {
      resolve(`内部 promise`)
    })
  })
  .then(res => {
    console.log(res)
  })

/**
 * 输出结果如下:
 * 外部 promise
 * 内部 promise
 */

通过以上的输出结果不难判断,当 then 函数返回的是一个 promise 时,promise 并不会直接将这个 promise 传递到下一个 then 函数,而是会等待该 promise resolve 后,将其 resolve 的值,传递给下一个 then 函数,找到我们实现的代码的 then 函数部分,做以下修改:

then(onFulfilled, onRejected) {
   const self = this
   if (this.status === 'pending') {
       return new MyPromise((resolve, reject) => {
       this.onFulfilledFunctions.push(() => {
           const thenReturn = onFulfilled(self.value)
           if (thenReturn instanceof MyPromise) {
               // 当返回值为 promise 时,等该内部的 promise 状态扭转时,同步扭转外部的 promise 状态
               thenReturn.then(resolve, reject)
           } else {
               resolve(thenReturn)
           }
       })
       this.onRejectedFunctions.push(() => {
           const thenReturn = onRejected(self.value)
           if (thenReturn instanceof MyPromise) {
               // 当返回值为 promise 时,等该内部的 promise 状态扭转时,同步扭转外部的 promise 状态
               thenReturn.then(resolve, reject)
           } else {
               resolve(thenReturn)
           }
       })
       })
   } else if (this.status === 'fulfilled') {
       return new MyPromise((resolve, reject) => {
           const thenReturn = onFulfilled(self.value)
           if (thenReturn instanceof MyPromise) {
               // 当返回值为 promise 时,等该内部的 promise 状态扭转时,同步扭转外部的 promise 状态
               thenReturn.then(resolve, reject)
           } else {
               resolve(thenReturn)
           }
       })
   } else {
       return new MyPromise((resolve, reject) => {
           const thenReturn = onRejected(self.value)
           if (thenReturn instanceof MyPromise) {
               // 当返回值为 promise 时,等该内部的 promise 状态扭转时,同步扭转外部的 promise 状态
               thenReturn.then(resolve, reject)
           } else {
               resolve(thenReturn)
           }
       })
   }
}

(4) 之前的 promise 实现代码仍然缺少很多细节逻辑,下面会提供一个相对完整的版本,注释部分是增加的代码,并提供了解释。

class MyPromise {
  constructor(executor) {
    this.executor = executor
    this.value = null
    this.status = 'pending'
    this.onFulfilledFunctions = []
    this.onRejectedFunctions = []
    const resolve = value => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'
        this.onFulfilledFunctions.forEach(onFulfilled => {
          onFulfilled()
        })
      }
    }
    const reject = value => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'rejected'
        this.onRejectedFunctions.forEach(onRejected => {
          onRejected()
        })
      }
    }
    this.executor(resolve, reject)
  }

  then(onFulfilled, onRejected) {
    const self = this
    if (typeof onFulfilled !== 'function') {
      // 兼容 onFulfilled 未传函数的情况
      onFulfilled = function() {}
    }
    if (typeof onRejected !== 'function') {
      // 兼容 onRejected 未传函数的情况
      onRejected = function() {}
    }
    if (this.status === 'pending') {
      return new MyPromise((resolve, reject) => {
        this.onFulfilledFunctions.push(() => {
          try {
            const thenReturn = onFulfilled(self.value)
            if (thenReturn instanceof MyPromise) {
              thenReturn.then(resolve, reject)
            } else {
              resolve(thenReturn)
            }
          } catch (err) {
            // catch 执行过程的错误
            reject(err)
          }
        })
        this.onRejectedFunctions.push(() => {
          try {
            const thenReturn = onRejected(self.value)
            if (thenReturn instanceof MyPromise) {
              thenReturn.then(resolve, reject)
            } else {
              resolve(thenReturn)
            }
          } catch (err) {
            // catch 执行过程的错误
            reject(err)
          }
        })
      })
    } else if (this.status === 'fulfilled') {
      return new MyPromise((resolve, reject) => {
        try {
          const thenReturn = onFulfilled(self.value)
          if (thenReturn instanceof MyPromise) {
            thenReturn.then(resolve, reject)
          } else {
            resolve(thenReturn)
          }
        } catch (err) {
          // catch 执行过程的错误
          reject(err)
        }
      })
    } else {
      return new MyPromise((resolve, reject) => {
        try {
          const thenReturn = onRejected(self.value)
          if (thenReturn instanceof MyPromise) {
            thenReturn.then(resolve, reject)
          } else {
            resolve(thenReturn)
          }
        } catch (err) {
          // catch 执行过程的错误
          reject(err)
        }
      })
    }
  }
}

(5)至此一个相对完整的 promise 已经实现,但他仍有一些问题,了解宏任务、微任务的同学一定知道,promise 的 then 函数实际上是注册一个微任务,then 函数中的参数函数并不会同步执行。
查看如下代码:

new Promise((resolve,reject)=>{
    console.log(`promise 内部`)
    resolve()
}).then((res)=>{
    console.log(`第一个 then`)
})
console.log(`promise 外部`)

/**
 * 输出结果如下:
 * promise 内部
 * promise 外部
 * 第一个 then
 */

// 但是如果使用我们写的 MyPromise 来执行上面的程序

new MyPromise((resolve,reject)=>{
    console.log(`promise 内部`)
    resolve()
}).then((res)=>{
    console.log(`第一个 then`)
})
console.log(`promise 外部`)
/**
 * 输出结果如下:
 * promise 内部
 * 第一个 then
 * promise 外部
 */

以上的原因是因为的我们的 then 中的 onFulfilled、onRejected 是同步执行的,当执行到 then 函数时上一个 promise 的状态已经扭转为 fulfilled 的话就会立即执行 onFulfilled、onRejected。
要解决这个问题也非常简单,将 onFulfilled、onRejected 的执行放在下一个事件循环中就可以了。

if (this.status === 'fulfilled') {
  return new MyPromise((resolve, reject) => {
    setTimeout(() => {
      try {
        const thenReturn = onFulfilled(self.value)
        if (thenReturn instanceof MyPromise) {
          thenReturn.then(resolve, reject)
        } else {
          resolve(thenReturn)
        }
      } catch (err) {
        // catch 执行过程的错误
        reject(err)
      }
    })
  }, 0)
}
上一篇 下一篇

猜你喜欢

热点阅读