多多读书
732 字
4 分钟
JS延迟模式

通常,在处理多个相互依赖的 Promise 函数时,我们会使用Promise.all来等待所有值返回以进行处理。然而,有些情况下无法使用Promise.all,比如在事件监听程序中。那么在这种情况下,我们应该如何处理呢?

以下面的例子为例:

let valueB
function A(b) {
  // ...
}
document.addEventListener('message', () => {
  A(valueB)
})
async function B() {
  // 包含其他异步程序
  valueB = 1
}
B()

A 在一个事件回调中执行,而 B 是一个异步函数,A 函数调用需要依赖于 B 函数的结果。当触发事件时,B 的执行并不一定已经完成,因此我们需要等到 B 完成执行后才能调用 A。那么要正确调用 A,我们可以采取一种比较直接的方式,即在调用 A 之前判断 B 是否已经完成,同时在 B 中判断是否已经触发了事件从而调用 A。

let valueB
let BFinished = false // 判断 B 是否已完成执行
let AReceived = false // 判断是否已触发事件
function A(b) {
  // ...
}
document.addEventListener('message', () => {
  AReceived = true
  if (BFinished) {
    // B 已完成执行,调用 A
    A(valueB)
  }
})
async function B() {
  // 包含其他异步程序
  valueB = 1
  BFinished = true
  if (AReceived) {
    // 事件已经触发,需要调用 A
    A(valueB)
  }
}
B()

这种方式需要维护的状态变多了,原本只有一个地方调用 A,现在却变成了两个,导致耦合度很高。在这个简单的示例中可能还好,但是如果代码量增加的话就会变得很复杂。那么有没有一种等待的模式呢?也就是说,在执行 A 时,如果 B 还未完成,就等待 B 完成后再执行 A。在这种情况下,Promise函数非常适合使用。Promise函数在没有调用resolve时会一直保持pending状态,而一旦调用了resolvepending状态就会立即结束并返回结果。因此,我们可以利用Promise函数来接收 B 的返回值,并在 B 中使用resolve来结束pending状态。

首先,我们需要创建一个延迟对象,该对象包含一个promise、一个resolve方法和一个reject方法,以便我们可以在不同的地方进行分别调用。

class Defer {
  promise: Promise<any>
  resolve: (value: any) => void = () => {}
  reject: (reason?: any) => void = () => {}
  constructor() {
    this.promise = new Promise((resolve, reject) => {
      this.resolve = resolve
      this.reject = reject
    })
    Object.freeze(this)
  }
}

然后,我们可以利用这个延迟对象实现延迟求值。

const deferred = new Defer()
function A(b) {
  // ...
}
document.addEventListener('message', async () => {
  const valueB = await deferred.promise // 使用 promise 接收 B 的值
  A(valueB)
})
async function B() {
  // 包含其他异步程序
  deferred.resolve(1) // 调用 resolve 结束 pending
}
B()

使用这种方式,逻辑更加清晰,而且不需要处理各种状态变更。

JS延迟模式
https://fuwari.vercel.app/posts/20230915/
作者
我也困了
发布于
2023-09-15
许可协议
CC BY-NC-SA 4.0