了解Node.JS使用async.waterfall如何执行外部函数

我需要使用async.js模块执行asynchronous函数。 但是当我执行外部函数时,我遇到了一些问题。

代码通过。

但是当我更改全局variables为本地variables,我不能设置使用参数。

var async = require('async'); var ogs = require('open-graph-scraper'); // global variables var param1 = {url: 'http://www.google.com/'}; var param2 = {url: 'https://www.yahoo.com/'}; function function1(callback){ ogs(param1, function(error, data1) { callback(null, data1); }); } function function2(data1, callback){ ogs(param2, function(error, data2) { callback(null, data1, data2); }); } function function3(data1, data2, callback){ console.log(data1); console.log("---------------"); console.log(data2); } (function temp() { async.waterfall([function1, function2, function3], function(err, result){ console.log(result); console.log(err); if(err) console.log(err); } ); })(); 

如果param1和param2更改为局部variables,像这样..

 (function temp() { var param1 = {url: 'http://www.google.com/'}; var param2 = {url: 'https://www.yahoo.com/'}; async.waterfall([function1, function2, function3], function(err, result){ console.log(result); console.log(err); if(err) console.log(err); } ); })(); 

如何在function1()或function2()中使用“param”

我不能改变本地的functiontypes

 async.waterfall([ function(callback){ }, function(data,callback){ }], function(err){ if(err) console.log(err); } ); 

嘘! 我正在使用一些ES6语法,请至less在节点6上运行代码片段,好吗?


一个asynchronous任务可以被build模为一个需要callback的函数:

 function task(arg1, arg2, callback) { // ... callback(null, result); } task(arg1, arg2, (err, result) => { handle(result); }); 

但是有一个替代的惯例经常简化事情:

 function task(arg1, arg2) { // ... return Promise.resolve(result); } task(arg1, arg2).then(handle(result)); 

虽然这两个约定都是有道理的,但我已经看到第二种方法在实践中更有用,可以编写具有良好error handling的简单asynchronous代码。

最重要的要点是:

  • 函数返回一个值; asynchronous任务返回未来价值的承诺。
  • 当asynchronous任务完成时,它将该承诺标记为“已解决”。
  • 如果asynchronous任务失败,而不是引发exception,则将该承诺标记为“被拒绝”,同时出现错误。
  • 返回的承诺也是一个对象。 甚至在它完成之前,你可以用它做有用的事情。 运行asynchronous任务的代码不必与对任务结果感兴趣的代码位于同一位置。

承诺的一个重要的事情是,它们保证是asynchronous的,不像callback:

 // callbacks myTask(1, 2, (err, result) => { console.log("A"); }); console.log("B"); // can be AB or BA depending on myTask // promises myTask(1, 2).then(() => { console.log("A"); }) console.log("B"); // always BA 

这使代码更容易推理,但这也意味着,当你真正依赖第二个行为时,promise不会有帮助。

(阅读关于诺言的更多信息!)


初始点

好的,让我们回到你的代码。 首先,让我用一个虚拟的asynchronous函数replaceogs ,这样我们就可以在不联网的情况下使用一些代码:

 var async = require('async'); function ogs(param, callback) { let value = ["ogs", param]; setTimeout( () => callback(null, value), 20); } // global variables var param1 = {url: 'http://www.google.com/'}; var param2 = {url: 'https://www.yahoo.com/'}; function function1(callback){ ogs(param1, function(error, data1) { callback(null, data1); }); } function function2(data1, callback){ ogs(param2, function(error, data2) { callback(null, data1, data2); }); } function function3(data1, data2, callback){ console.log(data1); console.log("---------------"); console.log(data2); } (function temp() { async.waterfall([function1, function2, function3], function(err, result){ console.log(result); console.log(err); if(err) console.log(err); } ); })(); 

让我们试试这些承诺的事情

相当于返回承诺而不是callback的ogs可能如下所示:

 function ogs(param, callback) { // return a promise that resolves after 20ms return new Promise((resolve, reject) => { setTimeout(() => { let value = ["ogs", param]; resolve(value); }, 20); }); } 

因为ogs现在可以返回一个promise,所以在每个function使用它是很简单的:

 function function1(){ return ogs(param1); // call async task, obtain the promise for its result and return it directly } function function2() { return ogs(param2); } function function3(data1, data2){ console.log(data1); console.log("---------------"); console.log(data2); } 

如果您想在中间添加一些日志logging,那也很简单:

 function function2() { return ogs(param2).then(data2 => { console.log("inside function2", data2); return data2; }); } 

现在,每个步骤都是一个承诺返回的asynchronous任务,让我们把它们连接在一起! 最简单的方法是使用Promise.then直接:

 (function temp() { function1().then(data1 => { return function2().then(data2 => { return function3(data1, data2); }); }).catch(error => { console.error("There was a problem:", error); }) })(); 

这将运行function1 ,完成后,它将结果传递给function2 ,然后将两个结果传递给function3

并行运行的东西

可是等等! function2甚至不需要等待function1完成。 这是两个单独的请求。 我们可以同时启动他们两个。

 (function temp() { let data1Promise = function1(); let data2Promise = function2(); Promise.all([data1Promise, data2Promise]).then(([data1, data2]) => { return function3(data1, data2); }).catch(error => { console.error("There was a problem:", error); }) })(); 

Promise.all接受一组promise,并返回一个用一组结果解决的promise。 我们从数组中解压缩这些结果并将它们传递给function3

并行运行networking请求应允许您的应用运行速度更快。 赢得!

现在回到你原来的问题:

如何摆脱全局?

我们完全控制了function1function2的签名,所以让我们使用它! 让我们有这些函数把参数作为参数,而不是看全局variables。 喜欢这个:

 function function1(param){ return ogs(param); } function function2(param) { return ogs(param, {"some other options": true}); } 

这些function现在看起来超级相似! 也许你可以使用一个(或者直接删除它们并直接调用ogs )?

删除全局variables之后,我们的代码如下所示:

 (function temp() { let param1 = {url: 'http://www.google.com/'}; let param2 = {url: 'https://www.yahoo.com/'}; let data1Promise = function1(param1); let data2Promise = function2(param2); Promise.all([data1Promise, data2Promise]).then(([data1, data2]) => { return function3(data1, data2); }).catch(error => { console.error("There was a problem:", error); }) })(); 

但是我真的需要我的function顺序运行!

如果function2实际上无法启动没有function1的结果呢?

 function function1(param) { return ogs(param); } function function2(data1, param) { return ogs(param2, {"some other options": data1}); } 

我们可以恢复到嵌套的第一个版本,但我们也可以尝试一些更加整洁:

 (function temp() { let param1 = {url: 'http://www.google.com/'}; let param2 = {url: 'https://www.yahoo.com/'}; let data1Promise = function1(param1); let data2Promise = data1Promise.then(data1 => function2(data1, param2)); // ! Promise.all([data1Promise, data2Promise]).then(([data1, data2]) => { return function3(data1, data2); }).catch(error => { console.error("There was a problem:", error); }) })(); 

async.waterfall有什么不同?

waterfall需要你写这些函数,以便他们用下一步需要的所有信息调用callback函数。 stream程如下所示:

 function1 -> (data1) function2 -> (data1, data2) function3 

想象一下,如果你不得不连锁10个电话,而不是2个…基本上步骤2需要知道步骤3,4,5,6可能需要什么。

有了承诺,你可以通过从每个任务返回一个数组来做同样的事情,但是你可以做得更好:

不再需要用function1function2来包装ogs ,因为你可以这样做:

 Promise.all([ogs(...), ogs(...), ogs(...)]).then(allResults) 

所有的东西都会收集在一个数组中。

非常相关的阅读: 当一个承诺依赖于另一个时,Bluebird的Promise.all()方法

但是我的API不会回复诺言!

我希望我现在有你的承诺,但你仍然坚持这个签名:

 ogs(options, function (err, results) {...}) 

我们希望将其转换为如下forms:

 ogsAsync(options) -> Promise 

手动使用Promise构造函数很简单:

 function ogsAsync(options) { return new Promise((resolve, reject) => { ogs(options, (err, results) => { if (err) { reject(err); } else { resolve(results); } }); }); } 

但你可能不需要,因为看起来你的库已经返回一个promise ,所以你可以直接调用osg(options)它已经返回一个promise好极了!

但是,如果你不得不使用一个不提供承诺的库(比如redis或者多个node标准库), Bluebird提供了一个很好的工具来自动将callback风格的任务包装到承诺风格的任务中。

希望有所帮助!

所以我平时做的是设置一种“引导”function,作为我瀑布中的第一个function,将参数引入,并启动链条传递给他们。

 function start(params){ params = params || {}; // make sure you have at least an empty object here return function(callback){ // do something callback(null, params); // error is always the first cb param for most things } } function second(params, callback){ // do something else. Maybe extend the params object params.newProp = "foo"; callback(null, params); } // later, maybe in another module async.waterfall([ start({foo : 'bar'}), second ], function result(e, res){ // handle result });