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

时间:2016-07-22 12:53:43

标签: javascript node.js asynchronous waterfall

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

代码很顺利。

但是当我将全局变量更改为局部变量时,我无法使用参数进行设置。

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更改为局部变量,就像这样..

(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”

我无法更改本地的功能类型

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

2 个答案:

答案 0 :(得分:2)

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

可以将异步任务建模为接受回调的函数:

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));

虽然两种惯例都有意义,但我已经看到第二种方法在实践中更有用,可以编写简单的异步代码并具有良好的错误处理能力。

最重要的要点是:

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

关于promises的重要一点是,与回调不同,它们保证是异步的:

// 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

这使得代码更易于推理,但这也意味着当您实际依赖第二种行为时,承诺将没有用处。

(Read up more on Promises!)

起点

好的,让我们回到你的代码。首先让我用虚拟异步函数替换ogs,这样我们就可以在没有网络的情况下使用一些代码:

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);
        }
    );
})();

让我们试试这些承诺

返回承诺而不是回调的等效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现在返回一个承诺,所以在每个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);
}

如果您想在中间添加一些日志记录,那也很容易:

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

现在每个步骤都是一个承诺返回异步任务,让我们将它们连接在一起!最简单的方法是直接使用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

并行运行网络请求应该可以让您的应用程序以更快的速度运行。赢了!

现在回到原来的问题:

如何摆脱全局?

我们可以完全控制function1function2的签名,让我们使用它!让这些函数将param作为参数,而不是查看全局变量。像这样:

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

这些功能现在看起来非常相似!也许你可以只使用一个(或者只是放弃它们并直接呼叫ogs)。

删除全局变量后,我们的代码现在看起来像这样:

(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);
    })
})();

但我真的需要我的功能按顺序运行!

如果没有function2的结果,function1实际上无法启动怎么办?

function function1(param) {
    return ogs(param);
}

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

我们可以使用嵌套then恢复到第一个版本,但我们也可以尝试更整洁的内容:

(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。流程如下:

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

想象一下,如果你必须链接10个电话而不是2个......基本上第2步需要知道第3,4,5,6步可能需要什么。

使用promises,你可以通过从每个任务返回一个数组来做同样的事情,但你可以做得更好:

不再需要使用ogsfunction1 包裹function2,因为您可以这样做:

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

并且所有内容都以数组形式收集。

非常相关的阅读:Bluebird's Promise.all() method when one promise is dependent on another

但我的API不会返回承诺!

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

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

我们希望将其转换为:

ogsAsync(options) -> Promise

使用Promise构造函数手动操作非常简单:

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

但您可能不需要,因为looks like your library already returns a promise,所以您可以直接致电osg(options) - 它已经返回承诺耶!

但是,如果您必须使用尚未提供承诺的库(如redis或大多数node标准库),Bluebird提供了一个很好的实用工具自动将回调式任务包装成承诺式任务。

希望有所帮助!

答案 1 :(得分:0)

所以我通常做的是设置一种' bootstrap'作为我瀑布中的第一个,它接受了参数并开始向前传递它们。

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 
  });