Skip to content

Promise

语法

js
const promise = new Promise(function(resolve, reject) {
  // executor(执行函数) 
})

传递给 new Promise的函数称之为 executor。当 promise 被创建时,它会被自动调用并会产生一个结果。

Promise执行过程中总共会有3种状态:

  • pending 表明正在执行任务
  • fulfilled 表明任务完成
  • rejected 表明有错误发生

Promise的状态只会从pending -> fulfilled 或者 pending -> rejected,fulfilled与rejected不能相互转换,且过程不可逆。

当 executor 完成任务时,应调用下列的方法之一:

  • resolve(value) —— 任务已经完成 (将 state 设置为 "fulfilled",将 result 设置为 value
  • reject(error) —— 表明有错误发生 (将 state 设置为 "rejected",将 result 设置为 error

Promise

要点

  • executor 应该完成任务(一般需要时间),然后调用 resolve 或 reject 来改变 promise 对象的对应状态。
  • executor 只会调用 resolve 或 reject。Promise 的最后状态一定会变化。

示例

js
const promise = new Promise(function(resolve, reject) {
  // 当 promise 被构造时,函数会自动执行

  // 在 1 秒后,任务完成
  setTimeout(() => resolve("done!"), 1000)
})

const promise2 = new Promise(function(resolve, reject) {
  // 在 1 秒后,任务失败
  setTimeout(() => reject(new Error("error!")), 1000)
})

".then"和".catch"

语法:

js
promise.then(
  function(result) { /* handle a successful result */ },
  function(error) { /* handle an error */ }
)
  • 第一个函数参数在 promise 为 resolved 时被解析,然后得到结果并运行。第二个参数在状态为 rejected 并得到错误时使用。

示例

js
/* 成功 */
const promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("done!"), 1000)
})

// resolve 在 .then 中运行第一个函数
promise.then(
  result => console.log(result), // 在 1 秒后显示“done!”
  error =>  console.log(error) // 不会运行
)

/* 错误 */
const promise2 = new Promise(function(resolve, reject) {
  setTimeout(() => reject(new Error("error!")), 1000)
})

// reject 在 .then 中运行第二个函数
promise2.then(
  result => console.log(result), // 无法运行
  error => console.log(error) // 在 1 秒后显示 "Error: error!"
)

我们可以只为“.then”提供一个参数,只得到成功的结果promise.then()如果要捕获错误,可以将第一个参数传为null,交由第二个函数处理promise.then(null, function)或者用一种更直观清晰的写法是用“.catch”promise.catch()

js
promise.then(res => {
  // 处理成功结果
}).catch(err => {
  // 处理错误结果
})

// .catch(f) 是 .then(null, f) 的模拟, 是一种简写

.then和.catch都是异步的

当 .then/catch 处理器应该执行时,它会首先进入内部队列。JavaScript 引擎从队列中提取处理器,并在当前代码完成时执行。

js
const promise = new Promise(resolve => resolve("done!"))

promise.then(console.log) // 完成!(在当前代码完成之后)

console.log("code finished") // 先显示

// code finished
// done!

链式调用

.then/catch(handler) 返回一个新的 promise,它根据处理程序的作用而改变

  • 如果它返回一个值或在没有 return(同 return undefined)的情况下结束,则新的 promise 将变为 resolved,并且用该值作参数调用最近的 resolve 处理程序(.then 的第一个参数)。
  • 如果它抛出错误,则新的 promise 将 rejected,并且用该错误作参数调用最接近的 reject 处理程序(.catch 或 .then 的第二个参数)
js
new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000) 
}).then(res => {
  console.log(res) // 1
  return res * 2
}).then(res => { 
  console.log(res); // 2
  return res * 2
}).then(res => {
  console.log(res) // 4
}).then(res => {
  console.log(res) // undefined
  throw new Error('error')
}).catch(err => {
  console.log(err) // Error: error
})

.catch 并不是始终在链的结束处,我们应该准确的放置在我们想要处理错误的地方。

Promise API

在 Promise 类中,有 4 中静态方法。

Promise.resolve()

语法:

js
const promise = Promise.resolve(value)

// 根据给定的 value 值返回 resolved promise

/*
 * 与下述语法等价 
*/
const promise2 = new Promise(resolve => resolve(value))

一般会用于封装某个方法,确保函数会返回一个promise

Promise.reject()

语法:

js
const promise = Promise.reject(value)

// 创建一个带有 error 的 rejected promise。

/*
 * 与下述语法等价 
*/
const promise2 = new Promise((resolve, reject) => reject(value))

Promise.all()

该方法并行运行多个 promise,并等待所有 promise 完成

语法:

js
const promise = Promise.all(iterable)

// iterable 是一个包含promise可迭代对象,一般都是数组

示例:

js
Promise.all([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 3000)), // 1
  new Promise((resolve, reject) => setTimeout(() => resolve(2), 2000)), // 2
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 1000))  // 3
]).then(console.log)

// [ 1, 2, 3 ]

Promise.all() 中传参与出参顺序是相同的。所以此处虽然第一个Promise需要完成的时间比其他长,但仍然是结果数组中的第一个。

如果任何 promise 为 rejected,Promise.all 就会立即以 error reject。

js
Promise.all([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("error!")), 2000)),
  new Promise((resolve, reject) => setTimeout(() => console.log(3), 3000))
]).catch(console.log)

// Error: error!
// 3

这里的第二个 promise 在两秒内为 reject。这立即导致了对 Promise.all 的 reject,因此 .catch 被执行。但是:其他的promise会继续执行,只是结果会被忽略。

Promise.race()

与 Promise.all 类似,但不会等待所有promise都完成 —— 只等待第一个完成(或者有 error),然后继续执行。

语法:

js
const promise = Promise.race(iterable)

示例:

js
Promise.race([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("error!")), 2000)),
  new Promise((resolve, reject) => setTimeout(() => console.log(3), 3000))
]).then(console.log)

// 1
// 3
/* race 就是竞赛的意思,返回结果最快的那一个就是第一个。*/

第一个结果或者错误会成为整个 Promise.race 的结果 第一个promise得到处理后,其他的所有结果或者错误都会被忽略。

Promise.all与Promise.race的比较:

  • 两者接收的参数类型一致
  • 两者都会执行完所有的promise
  • Promise.all必须等到所有promise都成功才会返回结果数组,其中若有一个reject,则整个Promise.all就会reject。
  • Promise.race返回的永远是执行最快的那个promise的结果或者错误。

问题:Promise.all与Promise.race都无法拿到所有的返回结果(包括错误的结果),这在我们的现实开发中其实是存在问题的,因为我们往往想要拿到所有的结果,特别是在与后台的通信中。以下为解决方案:

点击查看代码
js
const taskIds = [1, 3, 4, 6, 8]

const taskQueue = taskIds.map(id => ajax(id).catch(err => err)) // 这里的.catch为关键操作

Promise.all(taskQueue).then(res => {
  console.log(res)
}).catch(err => {
  console.error(err)
})

/* 模拟异步操作 */
function ajax(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id === 6) {
        reject(new Error('error~'))
      }
      resolve({'id': id, 'name': id + 'ddd'})
    }, 1000*id)
  })
}
// resolve与reject的结果都会被返回
/**
 * [ 
 *  { id: 1, name: '1ddd' },
    { id: 3, name: '3ddd' },
    { id: 4, name: '4ddd' },
    Error: error~
      at Timeout.setTimeout [as _onTimeout] (D:\test-demo\somedemo\promise.js:86:16)
      at ontimeout (timers.js:436:11)
      at tryOnTimeout (timers.js:300:5)
      at listOnTimeout (timers.js:263:5)
      at Timer.processTimers (timers.js:223:10),
    { id: 8, name: '8ddd' } ]
 */

要点:我们不能改变 Promise.all 的执行方式:如果它检测到 error,就会 reject 它。因此我们需要避免任何 error 发生。即如果发生error,我们仍然要将它返回。

  • .catch 会对所有的 promise 产生 error,然后正常返回。根据 promise 的执行方式,只要 .then/catch 处理器返回值(无论是 error 对象或其他内容),执行流程就会“正常”进行。
  • 因此 .catch 会将 error 作为“正常”结果返回给外部的 Promise.all。

Promise.allSettled()

Promise.allSettled() 能拿到所有promise执行完成的结果,无论每个promise是resolve还是reject;因此它可以用于想要得到所有promise执行结果的场景,而不是像Promise.all方法那样只要有任意一个promise被reject就无法拿到最后的结果。

Promise.any()

Promise.any() 返回第一个被resolve的值,只要有一个promise被resolve,它就会立即返回,所有它不会等待其他的promise完成;也就是说该方法忽略所有被reject的promise,直到第一个被resolve的promise。

Promise模拟实现

点击查看代码
js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  constructor(executor) {
    try {
      executor(this._resolve, this._reject)
    } catch (e) {
      this._reject(e)
    }
  }

  state = PENDING
  result = undefined
  reason = ''
  fulfilledCallbacks = []
  rejectedCallbacks = []

  _resolve = value => {
    if (this.state !== PENDING) return
    this.state = FULFILLED
    this.result = value
    this.fulfilledCallbacks.forEach(cb => cb())
  }

  _reject = reason => {
    if (this.state !== PENDING) return
    this.state = REJECTED
    this.reason = reason
    this.rejectedCallbacks.forEach(cb => cb())
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
    const self = this
    const promise2 = new MyPromise((resolve, reject) => {
      if (self.state === FULFILLED) {
        setTimeout(() => {
          try {
            const res = onFulfilled(self.result)
            self._resolvePromise(promise2, res, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      } else if (self.state === REJECTED) {
        setTimeout(() => {
          try {
            const x = onRejected(self.reason)
            self._resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      } else if (self.state === PENDING) {
        self.fulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const res = onFulfilled(self.result)
              self._resolvePromise(promise2, res, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
        self.rejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onRejected(self.reason)
              self._resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
      }
    })
    return promise2
  }

  catch(onRejected) {
    return this.then(null, onRejected)
  }

  _resolvePromise(promise, x, resolve, reject) {
    console.log(x, '_resolvePromise inner')
    if (promise === x) {
      console.log('chaining cycle')
      return reject(new TypeError('chaining cycle'))
    }
    if (x instanceof MyPromise) {
      try {
        x.then(resolve, reject)
      } catch (e) {
        reject(e)
      }
    } else {
      resolve(x)
    }
  }

  static resolve(param) {
    if (param instanceof MyPromise) {
      return param
    }
    return new MyPromise(resolve => resolve(param))
  }

  static reject(reason) {
    return new MyPromise((resolve, reject) => reject(reason))
  }

  static all(promises) {
    return new MyPromise((resolve, reject) => {
      const length = promises.length
      let index = 0
      const result = []
      for(let i = 0; i < length; i++) {
        MyPromise.resolve(promises[i]).then(res => {
          index++
          result[i] = res
          if (index === length) {
            resolve(result)
          }
        }, reason => {
          reject(reason)
        })
      }
    })
  }

  static allSettled(promises) {
    return new MyPromise((resolve, reject) => {
      const length = promises.length
      let index = 0
      const result = []
      for(let i = 0; i < length; i++) {
        MyPromise.resolve(promises[i]).then(res => {
          index++
          result[i] = {
            status: 'fulfilled',
            value: res
          }
          if (index === length) {
            resolve(result)
          }
        }, reason => {
          index++
          result[i] = {
            status: 'rejected',
            reason
          }
          if (index === length) {
            resolve(result)
          }
        })
      }
    })
  }

  static race(promises) {
    return new MyPromise((resolve, reject) => {
      for(const promise of promises) {
        MyPromise.resolve(promise).then(res => {
          resolve(res)
        }, reason => {
          reject(reason)
        })
      }
    })
  }
}

测试

打开控制台,点击下方Result,查看console

其他实现

Updated at: