【问题标题】:Crazy simultaneous javascript array concat interferes with itself疯狂的同时 javascript 数组 concat 会干扰自己
【发布时间】:2021-09-01 12:09:26
【问题描述】:

我花了太多时间来寻找这个错误。所以这是我的代码。显然,当我使用 push 时,数组最终是完整的,但是当我使用 concat 时,估计有 50% 的机会我不会得到所有连接的项目,因为 concats 似乎同时运行。我不相信这是可能的,所以任何人都可以向我解释为什么版本 1 有效,但版本 2 无效。

let employments: any[] = [];
let companyIds = ['Id1', 'Id2']
await Promise.all(
    companyIds.map( async ( companyId ) => {
        const companies = await getCompaniesWithId(companyId);
        // VERSION 1
        employments.push( ...(await mergeWithOtherSource( companies )) );
        
        // VERSION 2
        employments = employments.concat( await mergeWithOtherSource( companies ));
    } )
);
// VERSION 1 always returns 3 expected items as are in the databases
// VERSION 2 RANDOMLY returns between 1 and 3 items
return employments.length

【问题讨论】:

  • companyIds.map 不返回任何内容。没有什么可以等待的。 .map 旨在将一个数组映射到另一个数组。您只是推送到一个不正确的外部数组。
  • @zero298 companyIds.map 确实返回 Promises (Promise<void>[])
  • @OP 你能试着把await mergeWithOtherSource( companies )移到concat之外吗?
  • companyIds.map 确实返回承诺:jsfiddle.net/4db6ecxg/1
  • @appleapple 将 await 放在 concat 之外也可以解决问题。似乎 concat 在等待承诺之前引用了原始数组。

标签: javascript async-await


【解决方案1】:

这个答案提供了替代(而且更简单)的方法来做同样的事情。 (可能的原因在我的another answer中提供)

let companyIds = ['Id1', 'Id2']

/// mix await and then (some people may dislike this)
let employments = [].concat(
   await Promise.all(companyIds.map(companyId =>
      getCompaniesWithId(companyId).then(mergeWithOtherSource)
   ))
)

/// with await
let employments = [].concat(
   await Promise.all(companyIds.map( async ( companyId ) => {
      const companies = await getCompaniesWithId(companyId)
      return mergeWithOtherSource( companies )
   }))
)

/// with 2-step
let companys = await Promise.all(companyIds.map(getCompaniesWithId)
let employments = [].concat(await Promise.all(companys.map(mergeWithOtherSource)))

// can also use flat instead of concat
let employments = (await Promise.all(...)).flat()

【讨论】:

    【解决方案2】:

    这可能是因为this的引用是在评估它的参数之前获取的

    喜欢下面的

    // VERSION 1 step by step
    // employments.push( ...(await mergeWithOtherSource( companies )) );
    let O = employments
    let task = mergeWithOtherSource( companies )
    let items = await task;
    O.push(...value) // no problem, it's always the same object
        
    // VERSION 2 step by step
    // employments = employments.concat( await mergeWithOtherSource( companies ));
    let O = employments // multiple async thread may get the same object
    let task = mergeWithOtherSource( companies )
    let items = await task;
    let temp = O.concat(value) // may use outdate employments
    employments = temp
    

    所以两个异步函数可能会以这种方式执行

    // VERSION 2: possible execution order
    // "thread" in the sense of javascript `async`, which only one run at the same time, and only yield when `await`.
    
    let O = employments // 1st thread
    let task = mergeWithOtherSource( companies ) // 1st thread
    let items = await task // 1st thread, waiting
    
    let O = employments // 2nd thread
    let task = mergeWithOtherSource( companies ) // 2nd thread
    let items = await task // 2nd thread, waiting
    
    let temp = O.concat(items) // 1st thread
    employments = temp // 1st thread
    
    let temp = O.concat(items) // 2nd thread, now using outdated employments!
    employments = temp // 2nd thread, overwrite the object from thread 1
    
    

    考虑一下语句可能更容易看出

    GetEmployments().concat(GetSomething())
    

    GetEmployments() 将被评估一次,以生成上述示例中的O


    函数调用的一步一步(大致)

    // step by step `x.func(y)`
    let V = x
    let F = V.func
    let Arguments = [y]
    F.apply(V,Arguments)
    

    【讨论】:

    • 我认为你是对的,但我不明白 concat 在参数准备好之前如何做一些事情。似乎参数在 concat 之外等待,因此它无法引用“this”,因为据我所知,仅在等待参数时才调用该函数。正如我所提到的,该解决方案显然有效。
    • @JeremiasNater 如果您考虑它就像调用concat(this_object,items) 一样,那么很明显需要首先评估其中一个参数。 (在这种情况下,this_object 似乎首先被解析)
    • @JeremiasNater 或考虑这个简单的表达式foo().concat(bar())
    • 好吧,这不是我,但你的答案并不像代码一样读得好,因为它大多是伪代码,而且我也不太理解你的例子以及你为什么要制作它们。另一个示例完全在 npm.runkit.com 上运行,但为了让他被接受,他错过了我们提到的示例,充分展示了发生的情况
    • @JeremiasNater 很好,它确实运行并产生了相同的结果(如果你将线程 1 和线程 2 分开)。这是一步一步的解释。
    猜你喜欢
    • 2016-09-12
    • 2017-04-17
    • 1970-01-01
    • 2010-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-08
    • 2010-10-29
    相关资源
    最近更新 更多