Promises 与异步等待行为 javascript。并发模式

Promises vs async await behaviour javascript. Concurrency mode

我正在尝试避免异步函数的并发,以便等待此函数的第一次调用完成,然后我才能再次调用相同的函数:

const disallowConcurrency = (fn) => {
  let inprogressPromise = Promise.resolve();

  return async(...args) => {
    await inprogressPromise;
    inprogressPromise = inprogressPromise.then(() => fn(...args));
  };
};

const initCmp = async(arg) => {
  return new Promise((res) => {
    console.log('executed');
    setTimeout(() => res(console.log(arg)), 1000);
  });
};
const cmpConcurrentFunction = disallowConcurrency(initCmp);
cmpConcurrentFunction('I am called 1 second later');
cmpConcurrentFunction('I am called 2 seconds later');

所以我在这里创建了一个闭包,其中的承诺作为传递给内部函数的值。 return 函数将等待上一个调用(在第一个 运行 中它是一个已解决的 Promise )并且将分配给 inprogressPromise return 由 [=16] 编辑的承诺=] 在这种情况下将是一个新的 Promise return 由 initCmp 函数编辑。所以下次我们调用该函数时,它将等待上一个函数完成。至少我是这样理解的。我没看错吗?

但我不明白为什么如果我删除 await inprogressPromise 这个例子仍然有效:

    const disallowConcurrency = (fn) => {
  let inprogressPromise = Promise.resolve();

  return async (...args) => {
    inprogressPromise = inprogressPromise.then(() => fn(...args));
  };
};

所以 await 是没有必要的?为什么?

此外,我还希望它能起作用:

 const disallowConcurrency = (fn) => {
  let inprogressPromise = Promise.resolve();

  return async (...args) => {
    await inprogressPromise;
    inprogressPromise = fn(...args);
  };
};

因为我在想,首先我在等待之前的承诺完成,然后我会调用并分配 inprogressPromise 上的 returned 承诺。

但是不行,两个函数同时调用

谁能告诉我这是怎么回事?

如果您尝试该代码,您将看到第二次调用

cmpConcurrentFunction("I am called 2 seconds later")

将等待第一个承诺完成。

基本上我已经编写了一个节点包,它通过 npm 在 Web 应用程序中导入到浏览器中。 这个节点包库有一个 init 函数,它有一些异步代码,可以在代码的不同部分多次调用。 例如,如果被第二次调用,我想确保在尝试再次执行代码之前完成第一次执行。

但我的意思是试图理解为什么该代码有效以及为什么如果我使用最新版本它就无效

谢谢!

为了回答您的问题,让我先尝试解释一下您的代码是如何工作的。

了解代码的工作原理

以下步骤解释了代码的执行:

  1. 脚本执行开始

  2. 调用disallowConcurrency函数,传入initCmp作为参数。 cmpConcurrentFunction 被赋予 disallowConcurrency 函数

    的 return 值
  3. 第一次调用cmpConcurrentFunction,传入'I am called 1 second later'作为参数。在此调用期间,inprogressPromise 是由 Promise.resolve() 编辑的已解决的承诺 return。等待它暂停函数执行。

  4. 第二次调用cmpConcurrentFunction,传入'I am called 2 seconds later'作为参数。在第二次调用期间,inprogressPromise 仍然是 return 由 Promise.resolve() 编辑的已解决承诺。等待它暂停函数执行。

  5. 脚本同步执行到此结束。事件循环现在可以开始处理 micro-task 队列

  6. 由于第一次调用 cmpConcurrentFunction 而暂停的函数被恢复,调用 Promise.resolve() 承诺 return 上的 then() 方法。 inprogressPromise 的值通过为它分配一个由 inprogressPromise.then(...)

    编辑的新承诺 return 来更新
  7. 由于第二次调用 cmpConcurrentFunction 而暂停的函数已恢复。从第 6 步,我们知道 inprogressPromise 现在是由 inprogressPromise.then(...) 编辑的一个承诺 return。因此,在其上调用 then(),我们只是在创建一个承诺链,在步骤 6 中创建的承诺链的末尾添加一个新的 then() 方法调用。

    此时,我们有一个看起来像这样的链:

    inProgressPromise
      .then(() => fn('I am called 1 second later'))
      .then(() => fn('I am called 2 seconds later')) 
    
  8. 调用第一个then方法的回调函数,依次调用fn参数,即initCmp函数。调用 initCmp 设置一个计时器,在 1 秒后解析承诺 initCmp returns。当承诺在 1 秒后得到解决时,'I am called 1 second later' 会记录在控制台上。

  9. 当第一个 then 方法的回调函数中第一次调用 initComp 函数的承诺 return 被解决时,它解决了承诺return 由第一个 then 方法编辑。这导致调用第二个 then 方法的回调函数。这再次调用 initComp 函数,然后 return 是一个新的 promise,它会在 1 秒后得到解决。

这解释了为什么您会在 2 秒后看到 'I am called 2 seconds later' 登录到控制台。


现在回答您的问题:

But i don't understand why this keeps working if i remove the await inprogressPromise

await inprogressPromise 除了暂停对 cmpConcurrentFunction 函数的调用外,在您的代码中没有任何作用。两个调用都等待 return 由 Promise.resolve() 编辑的相同承诺。

So the await is not necessary? Why?

因为您在控制台上看到的输出不是因为 await,而是因为 cmpConcurrentFunction 的两次调用所构造的承诺链(上面的第 7 步)功能。

Further more i was expecting this to work :

const disallowConcurrency = (fn) => {   
  let inprogressPromise = Promise.resolve();

  return async (...args) => {
    await inprogressPromise;
    inprogressPromise = fn(...args);   
  }; 
};

以上代码不能作为您的原始代码使用,因为现在未构建对您的原始代码产生的输出至关重要的承诺链。

使用 disallowConcurrency 的这种实现,您的代码执行如下所述:

  1. 调用cmpConcurrentFunction函数,传入'I am called 1 second later'作为参数

  2. 等待inprogressPromise暂停函数执行

  3. 第二次调用cmpConcurrentFunction,传入'I am called 2 seconds later'作为参数

  4. 等待 inprogressPromise 暂停函数执行。此时,cmpConcurrentFunction 的两个调用都已暂停,并且两个函数调用都在等待因调用 Promise.resolve().[=82 而创建的 same promise =]

    这是这里的关键点:cmpConcurrentFunction 函数的两次调用都在等待由 Promise.resolve() 创建的 same promise。为什么两个调用都在等待相同的承诺?因为 cmpConcurrentFunction 之前被第二次调用 第一次调用 cmpConcurrentFunction 被恢复并调用 initComp 函数

  5. 恢复cmpConcurrentFunction的第一次调用,调用initComp函数并将其return值赋给inprogressPromise.

    调用initComp函数设置一个定时器,在1秒

    后解析承诺initComp函数returns
  6. 恢复cmpConcurrentFunction的第二次调用,调用initComp函数并将其return值赋给inprogressPromise,覆盖当前值inprogressPromise 这是第一次调用 initComp 函数时 return 的承诺。

    调用initComp函数设置一个定时器,在1秒

    后解析承诺initComp函数returns

此时,initComp 函数已被调用两次,设置了两个独立的计时器,每个计时器在 1 秒后解析各自的承诺。

总结 disallowConcurrency 函数的原始实现与这个函数之间的区别是,在原始实现中,您有一个按顺序解决承诺的承诺链,而在第二个实现中n,你有两个 分开的 承诺(不相互依赖)并且它们在每个 1 秒后被解决。