无法访问 for 循环中使用的链式 Promise 内的外部范围变量

Posted

技术标签:

【中文标题】无法访问 for 循环中使用的链式 Promise 内的外部范围变量【英文标题】:Can not access outer scope variable inside chained promise used in for loop 【发布时间】:2020-08-05 06:04:16 【问题描述】:

我需要在for..loop 中执行async/await,但那样我就无法进行并行操作。 所以我将这些承诺推入一个变量promises 稍后我将对其执行promise.all()。这是我的代码。

const promises = [];
  for (let i = 0; i < todayAssignedJobs.length; i += 1) 
    promises.push(Leaderboard.findOrCreate(...).then((leaderboard, created) => 
      if (!created) 
        const rating = (todayAssignedJobs[i].rating + leaderboard.rating * leaderboard.jobs_completed) / (leaderboard.jobs_completed + 1);
        const commission = todayAssignedJobs[i].commission + leaderboard.commission;
        const jobsCompleted = leaderboard.jobs_completed + 1;
        Leaderboard.update(
          rating,
          commission,
          jobs_completed: jobsCompleted,
          updated_by: 'system',
        , 
          where: 
            id: leaderboard.id,
          ,
        );
      
      AssignedJob.update(
        is_leaderboard_generated: true,
      , 
        where: 
          id: todayAssignedJobs[i].id,
        ,
      );
    ));
  
await Promise.all(promises)

不知何故,当我Assigned.update(.., where: id: todayAssignedJobs[i].id ) 时,我无法获得 id 出现错误:

未处理的拒绝错误:WHERE 参数“id”具有无效的“未定义”值

有人可以解释发生了什么吗?另外,请建议我可以在下面做吗?

promises.push(async () =>  // I will use await here )

【问题讨论】:

我会记录todayAssignedJobs[i] 的值。我的猜测是该对象没有id 属性,因为使用let 的for 循环为i 提供了适当的词法范围,因此异步性不是问题。对于更简洁的方法,请注意您基本上是在重新实现 Array.prototype.map(),所以只需使用它而不是 for 循环来初始化 promises @PatrickRoberts 感谢您的建议。我用map 替换了for,现在它更干净了。 :) 一个then回调只接受一个参数,(leaderboard, created)没有意义 【参考方案1】:

问题是 findOrCreate() 返回一个包含两个值的数组 [ ..., boolean ] 我传递给他们(leaderboard, created) 这里created 总是undefinedleaderboardarray。 我进行了更改,现在它工作正常。

const promises = todayAssignedJobs.map((todayAssigned) => Leaderboard.findOrCreate(...).then(([leaderboard, created]) => 
    if (!created) 
      const rating = (todayAssigned.rating + leaderboard.rating * leaderboard.jobs_completed) / (leaderboard.jobs_completed + 1);
      const commission = todayAssigned.commission + leaderboard.commission;
      const jobsCompleted = leaderboard.jobs_completed + 1;
      Leaderboard.update(
        rating,
        commission,
        jobs_completed: jobsCompleted,
        updated_by: 'system',
      , 
        where: 
          id: leaderboard.id,
        ,
      );
    
    AssignedJob.update(
      is_leaderboard_generated: true,
    , 
      where: 
        id: todayAssigned.id,
      ,
    );
  ));

await Promise.all(promises);

【讨论】:

【参考方案2】:

只是对现有答案的一些进一步改进。

Leaderboard.update()AssignedJob.update()是需要等待的异步函数,所以需要将回调函数转换为async函数。这确保了在所有数据库操作完成之前,promise 不会被解析,而不仅仅是findOrCreate()

const promises = todayAssignedJobs.map(async todayAssigned => 
  const [leaderboard, created] = await Leaderboard.findOrCreate(...);

  if (!created) 
    const rating = (todayAssigned.rating + leaderboard.rating * leaderboard.jobs_completed) / (leaderboard.jobs_completed + 1);
    const commission = todayAssigned.commission + leaderboard.commission;
    const jobsCompleted = leaderboard.jobs_completed + 1;

    await Leaderboard.update(
      rating,
      commission,
      jobs_completed: jobsCompleted,
      updated_by: 'system',
    , 
      where: 
        id: leaderboard.id,
      ,
    );
  

  await AssignedJob.update(
    is_leaderboard_generated: true,
  , 
    where: 
      id: todayAssigned.id,
    ,
  );
);

await Promise.all(promises);

这种方法的一个更根本的问题是Leaderboard.findOrCreate()Leaderboard.update() 不是单个事务的一部分。这是有问题的,因为update() 取决于leaderboard 中条目的当前值,由于non-atomic 对条目的修改,这会在您的数据库中创建一个竞争条件:

const rating = (todayAssigned.rating + leaderboard.rating * leaderboard.jobs_completed) / (leaderboard.jobs_completed + 1);
const commission = todayAssigned.commission + leaderboard.commission;
const jobsCompleted = leaderboard.jobs_completed + 1;

每个方法都需要标记为单个事务的一部分。使用 sequelize.js,您可以使用 managed transaction 来实现:

const promises = todayAssignedJobs.map(
  todayAssigned => sequelize.transaction(async transaction => 
    const [leaderboard, created] = await Leaderboard.findOrCreate(
      transaction,
      ...
    );

    if (!created) 
      const rating = (todayAssigned.rating + leaderboard.rating * leaderboard.jobs_completed) / (leaderboard.jobs_completed + 1);
      const commission = todayAssigned.commission + leaderboard.commission;
      const jobsCompleted = leaderboard.jobs_completed + 1;

      await Leaderboard.update(
        rating,
        commission,
        jobs_completed: jobsCompleted,
        updated_by: 'system',
      , 
        transaction,
        where: 
          id: leaderboard.id,
        ,
      );
    

    await AssignedJob.update(
      is_leaderboard_generated: true,
    , 
      transaction,
      where: 
        id: todayAssigned.id,
      ,
    )
  )
);

await Promise.all(promises);

【讨论】:

我尝试了相同的方法,但 promises 在调用 await Promise.all(promises) 后返回了一个数组 [ [AsyncFunction], [AsyncFunction] ]。你能解释一下Promise.all(promises)是如何在这里解决它们的吗? 知道了,但我不是在这里谈论这个问题。请看这里。我尝试了同样的方法。 ***.com/questions/61337600/… 完全不一样,你的问题已经被别人彻底解答了。 但是这一行 第一个问题是 Promise.all 接受一个承诺数组,而不是一个函数数组——你当前的代码不会工作。在这里做同样的事情吗?将函数数组传递给Promise.all() @SujeetAgrahari 按照您的逻辑,let x = [1, 2, 3, 4, 5].map(v =&gt; v * 2) 将使x 成为一个函数数组。 promisespromises 数组,而不是函数。

以上是关于无法访问 for 循环中使用的链式 Promise 内的外部范围变量的主要内容,如果未能解决你的问题,请参考以下文章

Promise 链中的 for 循环中的 Promise

PromiseKit `Result .. is used warning` 在带有 for 循环的链式序列中

在 for 循环中使用 Promise

在 JS 中使用 Promise 循环

使用fetch时如何在for循环中动态循环多个promise?

在 for 循环中等待 promise