【问题标题】:Preventing simultaneous calls to the same Promise / async function防止同时调用同一个 Promise / async 函数
【发布时间】:2022-01-16 03:57:02
【问题描述】:

情况

我有一个异步函数,需要 ~X seconds 来解析。持续时间取决于网络延迟,因此无法预测。该功能由用户输入触发(想想“将当前表单值保存到服务器”)

我想阻止这个函数同时运行多次。

我还想对函数的调用“排队”,以便在适当的时候调用。

问题

(A)“耐心”异步函数的概念是否有一个术语。

(B) 是否有一种工具(类似于debounce)可以采用异步函数并将其转换为“耐心”函数。

【问题讨论】:

  • 为什么过度设计?为此分配一个承诺但未分配,并让函数为该承诺分配一个实际值。然后,只要承诺尚未解决,就不要在后续调用中重新分配它。至于名称:不是真的,它基本上是您在寻找一种适用于您的用例的锁定模式。有很多方法可以做到这一点,但 promise 有他们的决心,所以你不妨利用它。
  • 感谢!我确实最终做了类似的事情,但它更复杂,因为我确实需要调用在等待期间发出的“最近”保存请求。这几乎是一种反向去抖动的情况。我承认我很惊讶没有实用程序存在,因为在用户交互时将前端值保存到服务器似乎是一个非常常见的用例!
  • 我有一个 NPM 包可以做到这一点,检查async-await-queue,你必须设置一个并发 1 和 0 最小等待的队列。它还支持优先级。
  • 为什么这会让事情变得更复杂? let mostRecent; function doSave() { if (!mostRecent) mostRecent = new Promise((resolve, reject) => { when you resolve, also set mostRecent to underfined }); 完成了吗?可能是时候在您的问题中显示实际代码,简化为最小形式但简化为人工用例,确保它仍然反映需要做的事情。
  • 例如参见stackoverflow.com/a/70326711/1048572stackoverflow.com/a/64955723/1048572。我还没有遇到过这方面的标准术语。

标签: node.js asynchronous npm promise


【解决方案1】:

此答案基于answer by @danh

函数queuePromise 接受一个返回promise 的函数。每当调用队列函数时,它的 promise 都会链接到前一次调用的 promise。

@danh 的回答与 this 的主要区别在于 queued 函数可以接收参数,promise 是一个简单的let 变量。

const queuePromise = fn => {
  let pending = Promise.resolve()
  return (...args) => {
    const func = () => fn(...args)
  
    return pending = pending.then(func, func)
  }

}

const demo = name => id => {
  console.log(`started ${name} ${id}`)
  
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // example of a failed promise in the middle of the chain
      if(id === 2) reject(`failed ${name} ${id}`)
      else resolve(`ended ${name} ${id}`)
    }, 500)
  })
}

const patientFoo = queuePromise(demo('foo'))
patientFoo(1).then(console.log).catch(console.log)
patientFoo(2).then(console.log).catch(console.log)
patientFoo(3).then(console.log).catch(console.log)

const patientBar = queuePromise(demo('bar'))
patientBar(1).then(console.log).catch(console.log)
patientBar(2).then(console.log).catch(console.log)
patientBar(3).then(console.log).catch(console.log)

【讨论】:

  • 这不能正确处理错误。如果一个demo 拒绝,则所有其他调用它都会受到影响
  • 已更新以处理失败的情况。
  • 等等,如果函数失败,它会被调用两次?
  • 是的。多个 catch/then 块。固定。
【解决方案2】:

下面,makePatient 将给定函数包装在另一个函数中,该函数仅在之前的调用链得到解决后才调用它。

function pause() {
  return new Promise(resolve => {
    setTimeout(resolve, 1000)
  })
}

function foo(str) {
  return pause().then(() => console.log(str));
}

function bar(str) {
  return pause().then(() => console.log(str));
}

function makePatient(fn) {
  let pending = Promise.resolve();

  return (...params) => {
    return pending = pending.then(() => fn(...params)).catch(console.log);
  }
}

let patientFoo = makePatient(foo);
let patientBar = makePatient(bar);

patientFoo('foo');
patientFoo('foo');
patientFoo('foo');

patientBar('bar');
patientBar('bar').finally(() => console.log("all the bars are closed"));

【讨论】:

  • 使用具有fn.name 属性的对象有什么意义?只需将 Promise 本身存储在变量中即可。
  • 这不能正确处理错误。如果一个fn 拒绝,所有其他调用它的也会受到影响
  • @Bergi,我的第二个想法是让一个对象使用fn.name 来按函数隔离承诺,确切地担心防止函数A 的链中的错误停止函数B 的链。 (并且错误地认为不同的finally 必须位于链的末端)。我认为您假设(可能是正确的)尽管先前调用出现错误,OP 仍希望执行后续调用。编辑使用单个链进行简化,将过去的错误作为默认设置,添加参数,并演示 finally
  • 感谢您的更新。这些链已经被对makePatient 的调用分隔开来,创建具有不同pending 变量的不同闭包,它仍然可以在您当前的代码中使用。关于错误处理,patientFoo('not foo').catch(e => console.log) 现在不会阻止后续调用正常运行,但不会执行 catch 处理程序,因为返回的承诺总是被履行。
猜你喜欢
  • 1970-01-01
  • 2015-10-31
  • 1970-01-01
  • 1970-01-01
  • 2012-11-18
  • 2021-08-21
  • 2015-07-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多