将同步代码重新格式化为异步

Posted

技术标签:

【中文标题】将同步代码重新格式化为异步【英文标题】:Reformatting syncronous code to asyncronous 【发布时间】:2017-05-19 03:00:05 【问题描述】:

我有一个同步遍历数组的方法,然后将数据结构返回给调用者。从逻辑上讲,该函数执行以下操作:

const myFunction = (inputarray) => 
  let returnarray = [];
  for (let element of inputarray)
    let returnelement = 
      a: element.A,
      b: element.B
    
    if (returnelement.b == 5)
      returnelement.c = a + b;
    
    returnarray.push(returnelement);
  
  return returnarray;

到目前为止一切顺利。但是,我意识到要填充returnelement.c 的属性,我需要从我的数据库中获取一些东西。我正在使用带有 mongoose 的 MongoDB 数据库。所以我将使用element.a 来获取我放入c 的内容。这意味着我需要进行异步调用来获取这些数据。

const myFunction = (inputarray) => 
  let returnarray = [];
  for (let element of inputarray)
    let returnelement = 
      a: element.A,
      b: element.B
    
    if (returnelement.b == 5)
      MyType.find( key: returnelement.b , (err, value) => 
        returnelement.c = value;
      );
    
    returnarray.push(returnelement);
  
  return returnarray;

这显然行不通。我的函数不会等待异步调用完成,它会在完成遍历数组后返回。

我想让函数在从数据库中获取所有值时将完成的结构返回给调用者。我想这意味着我需要将我的方法也重构为异步的,并在完成后调用回调,但我看不到一个干净的方法来做到这一点。我不能在 mongooses 回调中调用回调,因为我不能确定回调是最后一个完成的回调。我什至不知道 for 循环中有多少次迭代甚至会进行数据库调用。 (returnelement.b == 5)。

一个想法是做一个变量,以数组的长度开始,每次解析的回调减1,如果returnelement.b != 5减1,当回调中该值达到0时,我们调用回调对于我们的函数并传递returnarray。我希望有一个更好,更干净的版本吗?

【问题讨论】:

您可以考虑将整个事情移到异步回调或 Promise 中。 【参考方案1】:

您可以使用SynJS编写和执行同步代码:

function myFunction(inputarray) 
    var returnarray = [];
    for (var i=0; i<inputarray.length; i++)
        var element = inputarray[i];
        var returnelement = 
            a: element.A,
            b: element.B
        ;
        if (returnelement.b == 5)
            MyType.find( key: returnelement.b , function(err, value)
                returnelement.c = value;
                SynJS.resume(_synjsContext); //<-- indicates that callback is finished
            );
            SynJS.wait(); //<-- waits for callback to finish
        
        returnarray.push(returnelement);
    
    return returnarray;

然后你可以这样调用函数:

SynJS.run(myFunction, null, function (ret) 
    console.log('result:', ret);
);

【讨论】:

请看Is it acceptable to promote my own library as part of a real answer?【参考方案2】:

我希望有比这更好、更干净的版本吗?

Promises. 创建一个 promise 数组,每个元素一个,然后等待它们解析。由于您已经有一个数组,因此很容易将其映射到一个 promise 数组并将其传递给 Promise.all

const myFunction = (inputarray) => 
  return Promise.all(inputarray.map(element => new Promise((resolve, reject) => 
    let returnelement = 
      a: element.A,
      b: element.B
    
    if (returnelement.b == 5)
      MyType.find( key: returnelement.b , (err, value) => 
        returnelement.c = value;
        resolve(returnelement);
        // handle error?
        // reject(err);
      );
     else 
       resolve(returnelement);
    
  )));


myFunction([...])
  .then(resultArray => console.log(resultArray))
  .catch(error => console.log(error)))

当然,myFunction 现在返回了一个承诺,所以任何调用 myFunction 的东西都必须能够处理它。


正如@Tomalak 指出的那样,猫鼬实际上会返回承诺本身,因此我们可以将其简化为:

const myFunction = (inputarray) => 
  return Promise.all(inputarray.map(element => 
    let returnelement = 
      a: element.A,
      b: element.B
    
    if (returnelement.b == 5)
      return MyType.find( key: returnelement.b ).then(value => 
        returnelement.c = value;
        return returnelement;
      );
    
    return Promise.resolve(returnelement);
  ));

【讨论】:

Mongoose 有原生的 Promise 支持,这段代码可以再简化一点:mongoosejs.com/docs/promises.html @Tomalak:谢谢! 工作起来就像一个魅力,不需要任何库来让它工作。非常感谢!我想我需要更多地使用 Promises,因为代码在函数内部和调用代码中都非常好:) @ØyvindBråthen:是的,promise 提供了一个很好的 API 合约。 return Promise.resolve(returnelement); 不是必需的。你可以直接做return returnelement;Promise.all() 不区分承诺和普通值。【参考方案3】:

async 和 await 是你的朋友。你可以使用 Promise 感知生成器做一些事情,但我们会坚持使用更简单的。

function findData(key)

  return Promise.resolve(MyType.find( key ));


async function myFunction(inputarray)
  let returnarray = [];
  for (let element of inputarray)
    let returnelement = 
      a: element.A,
      b: element.B
    
    if (returnelement.b == 5)
      returnelement.c = await findData(returnelement.b);
    
    returnarray.push(returnelement);
  
  return returnarray;

最重要的是使用 asynquence 这样的库。

【讨论】:

您可能想解释需要做什么才能让async/await 工作。

以上是关于将同步代码重新格式化为异步的主要内容,如果未能解决你的问题,请参考以下文章

自动将冗长的 Java 代码重新格式化为 80 列并且仍然可以编译? [复制]

将对象数组重新格式化为键数组

将文本文件重新格式化为一行字符串[重复]

MySQL需要帮助将垂直数据重新格式化为水平

将列表重新格式化为分组结构

将一列字符串重新格式化为两个 [关闭]