【问题标题】:Passing data into node.js callbacks - the asynchronous issue [duplicate]将数据传递到 node.js 回调 - 异步问题 [重复]
【发布时间】:2014-07-23 11:19:05
【问题描述】:

以下面的代码为例,其中一个对象data 使用一些值进行初始化,然后由一些时间密集型函数处理,例如数据库访问。如果函数成功,成功的data 项目的名称将打印到控制台。否则,打印失败通知:

data = {first:  'someinfo',   second:  'somemoreinfo',   third:  'evenmoreinfo'};

for (var item in data) {
    timeIntensiveFunction(item, data[item], function(err) {
        if (!err) {
            console.log(item + ' processed successfully');
        } else {
            console.log(item + ' failed');
        }
    });
}

假设函数对所有三个数据项都成功,您会希望控制台显示此内容

first processed successfully
second processed successfully
third processed successfully

它会显示这个,假设第一次数据库访问比for循环花费更长的时间:

third processed successfully
third processed successfully
third processed successfully

这是因为控制台日志记录是在回调中完成的,只有在 for 循环完成后才合理地调用 ,因为 timeIntensiveFunction() 需要这样做长。 在调用第一个回调时,item 已经有了它的最后一个值,third

如何将 item 的“当前”值传递给回调?

【问题讨论】:

  • JavaScript 中的对象键没有排序。考虑一个简单的有序数组,如 ["first", "second", "third"] 或带有对象 [{id: "first"}, {id: "second"}, {id: "third"}] 的数组。
  • @maček 不是我想要的。问题在于,无论排序如何,节点都会以 some 的顺序处理三个data 项目。假设timeIntensiveFunction()for 循环完成的时间更长,所有三个控制台行都将显示item 的最后一个值,而不是timeIntensiveFunction() 评估的值
  • @go-oleg:实际上我认为这是一个有效的副本。 For-in 与 for-loop 没有区别,并且 OP 已经认识到,在循环结束后,回调是异步调用的,每一个必要的信息都在那里。

标签: javascript node.js asynchronous callback


【解决方案1】:

问题是因为它只使用最后一项调用回调。

您可以使用如下函数绑定每个项目。

var printStatus = function(item){
    return function(err) {
        if (!err) {
            console.log(item + ' processed successfully');
        } else {
            console.log(item + ' failed');
        }
    }
}

for (var item in data) {
    timeIntensiveFunction(item, data[item], printStatus(item));
}

【讨论】:

  • 这并不能保证项目是按顺序处理的
  • @maček 我认为它会给出,因为它只会调用函数printStatus 和各自的item
  • 它保证 timeIntensiveFunction 被顺序调用在项目上,但不保证回调的时间。但这首先是异步性和回调函数的全部意义所在。此外,这基本上与我的答案相同,但风格不同。
  • 如果你自己写了timeIntensiveFunction,这会有所帮助,如果你是从一个模块中得到的,你不会想把它包装在你自己的函数中
  • @TheEnvironmentalist 我想我没有明白你的意思,我没有包装timeIntensiveFunction。我只是包装了我无论如何都会定义的回调函数,而函数timeIntensiveFunction 需要一个回调并且它得到了它,我认为它来自模块还是我自己的并不重要。
【解决方案2】:

这是 javascript 中一个常见的带有闭包的“陷阱”。一种解决方法是将您的函数调用包装在一个匿名函数中并重新调整item 的范围。像这样:

for (var item in data) {
    (function(item){
        timeIntensiveFunction(item, data[item], function(err) {
           if (!err) {
               console.log(item + ' processed successfully');
           } else {
               console.log(item + ' failed');
           }
        });
   })(item);
}

【讨论】:

  • 我想过这个,但它很乱,我有大约二十个不同功能的包装器
  • 您始终可以使用像下划线 (underscorejs.org) 或 .forEach 语法(如果您的 javascript 实现支持的话)这样的库在一行而不是两行中执行迭代和回调函数。 /耸耸肩。这可能和你能得到的一样好。
【解决方案3】:

如果您正在寻找一个可以更轻松地处理异步任务的库,请查看caolan/async

var async = require("async");

var data = [{id: "first"}, {id: "second"}, {id: "third"}];

function timeIntensiveFunction(item, done) {
  // do something
  console.log("time intensive task started:", item.id);

  // err?
  // if (err) return done(err);

  done();
}

function processItem(item, done) {
  timeIntensiveFunction(item, function(err) {
    if (err) return done(err);
    console.log("task complete:", item.id);
    done();
  });
}

async.map(data, processItem);

输出

time intensive task started: first
task complete: first
time intensive task started: second
task complete: second
time intensive task started: third
task complete: third

对于希望学习如何在没有库的情况下执行此操作的用户,您可以查看此答案的修订历史记录。

【讨论】:

  • 这违背了for 循环的目的
  • 这就是重点; for 循环不能(轻易)用于异步串行处理。
  • 如果 async.series 不适合您,也许您想查看 Promises api 并链式 .then 调用您的 Promise 对象。
  • 但这给原本相对简单的过程增加了很多重量。 node.js for 循环是有原因的。 data 对象来自数据库。我不知道timeIntensiveFunction()会提前对哪些数据进行操作,甚至不知道会运行多少次。
  • 我提到了两个库,它们可以更轻松地处理这些事情,它们会清理很多语法。查看asyncPromises。我只会提供一个原生的 JS 解决方案。你可以进一步抽象它,让它变得更漂亮。
猜你喜欢
  • 2013-05-15
  • 2012-10-24
  • 2015-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-19
  • 2017-04-01
相关资源
最近更新 更多