Node.js - 异步 - if语句中的多个内部回调

时间:2018-05-10 05:00:53

标签: node.js azure-cosmosdb gremlin async.js

我使用的是Node.js和异步库,但我一直看到错误:Callback was already called

我想我理解为什么会收到错误,但我不知道是否可以执行以下操作/如何解决。

基本上我希望在外部回调完成之前完成两个内部回调。

所以我面对这个问题的代码如下:

async.forEachLimit(inData, 25, function (data, innercallback) {

    myJson.matches.forEach(function (oMatches) {
        if (data.$.id == oMatches.SourceId) {

            oMatches.ids.forEach(function (odId) {
                client.execute("g.addV('test').property('id', \"" + data.$.id + "\")", {},
                    function (err) {
                        setTimeout(function () { innercallback(err) }, 2000);
                    });


                client.execute("g.V('" + data.$.id + "').addE('matches').to(g.V('xyz'))", {},
                    function (err) {
                        setTimeout(function () { innercallback(err) }, 2000);
                    });
            })

        } //there is no else case.

    });

}, outercallback);

顺便说一下 - 我使用setTimeoutasync.forEachLimit来减少对Azure的请求数量(因为我没有太多)

2 个答案:

答案 0 :(得分:0)

Promise可用于确保异步回调的顺序。

看看async#AsyncFunction in the official document  async

  

无论我们何处接受节点式异步功能,我们也直接接受   接受ES2017异步功能。在这种情况下,异步功能将   不会传递最终的回调参数,并且任何抛出的错误都将是   用作隐式回调的错误参数,以及返回   value将用作结果值。 (即拒绝了   返回Promise成为错误的回调参数,并解决了   价值成为结果。)

async#forEachLimit的第三个参数是async#AsyncFunction,因此您可以从中返回一个承诺,然后解决承诺,表明其工作已经完成。

您的代码可以改进如下,

async.forEachLimit(inData, 25, function (data, innercallback) {

  // we will wait for all async function in here to complete
  // then call the innercallback
  var asyncPromises = []

  myJson.matches.forEach(function (oMatches) {

    if (data.$.id == oMatches.SourceId) {

      oMatches.ids.forEach(function (odId) {
        asyncPromises.push(new Promise(function (resolve, reject) {
          client.execute("g.addV('test').property('id', \"" + data.$.id + "\")", {},
            function (err) {
              setTimeout(function () {
                if (err) {
                  reject(err)
                  return
                }
                resolve();
              }, 2000);
            });
        }))

        asyncPromises.push(new Promise(function (resolve, reject) {
          client.execute("g.V('" + data.$.id + "').addE('matches').to(g.V('xyz'))", {},
            function (err) {
              setTimeout(function () {
                if (err) {
                  reject(err)
                  return
                }
                resolve();
              }, 2000);
            });
        }))

      })

    } //there is no else case.

  })

  // Here we can ensure that innercallback is called only for once
  Promise.all(asyncPromises)
    .then(function () {
      innercallback(null)
    })
    .catch(function (err) {
      // handle the error here
      innercallback(err)
    })

}, outercallback);

请注意,您应该确保在Node环境中获得Promise支持。在Node v6之后支持内置Promise。查看here

<强>更新

我误解了你在调用outercallback之前必须完成的内部回调。我已使用Promise.all更正了它,它将Promise数组作为参数并返回Promise,如果所有子Promise全部已解析或如果其中一个子Promise被拒绝,则被拒绝。请参阅Promise#all

更新(2018.05.14)

您必须确保从innercallback包中的AsyncFunction(即async#forEachLimit的第三个参数)收到的每个async在每个包中仅被一次调用迭代。特别是在每次迭代中执行Array#forEach时要小心,因为它可能会让您在迭代中多次调用innercallback

我更新了上面的代码块,我从innercallback的回调中删除了对client.execute的所有调用,并将所有client.execute放入了承诺中。之后,我将所有承诺收集到asyncPromises数组中。 Promise#all用于确保所有承诺都已解决(即所有client.execute已完成),然后最终调用innercallback。或者,如果上述承诺之一被拒绝并被Promise#catch捕获,请使用第一个参数调用innercallback作为错误原因。

答案 1 :(得分:0)

如果两个innercallback抛出错误,您正在调用client.execute两次。您可以使用async.parallel函数或Promise.all,这是async.parallel的示例,您还需要在innercallback块中调用else函数

async.forEachLimit(inData, 25, function(data, innercallback) {
    async.eachSeries(myJson.matches, function(oMatches, callback) {
        if (data.$.id == oMatches.SourceId) {
            async.eachSeries(oMatches.ids, function(odId, callback) {
                async.parallel([
                    function(callback) {
                        client.execute("g.addV('test').property('id', \"" + data.$.id + "\")", {}, callback);
                    },
                    function(callback) {
                        client.execute("g.V('" + data.$.id + "').addE('matches').to(g.V('xyz'))", {}, callback);
                    }
                ], callback);
            }, callback)

        } else {
            callback();
        }
    }, innercallback);
}, outercallback);

更新更新的代码,现在在async.eachSeries

的地方使用Array.forEach