【问题标题】:How to convert Step code to Async.JS (Step => waterfall, this.parallel)?如何将 Step 代码转换为 Async.JS(Step => 瀑布,this.parallel)?
【发布时间】:2014-03-04 09:40:15
【问题描述】:

几年前,我在试验 NodeJS,发现"Step" 库很好地清理了我的一些代码。当希望更新该代码时,我注意到 Step 上有一些“危险信号”。 (几年没有更新,只有 32 次提交等)

于是我环顾四周,发现Async.js,它有更多的功能和积极的维护。

看起来不错,一般。但是我开始尝试应用转换来代替它,并且可能没有采取正确的角度。

如果我没看错的话,Step 的核心功能似乎就是 Async.JS 所称的"waterfall" pattern。所以在 Step 你会写:

Step(
    function firstStepNoArgs() {
        foo.asyncCall(this);
    },
    function secondStep(err, argFromFoo) {
        if (err) {
            handleError(err);
        }

        bar.asyncCall(argFromFoo, 1, this.parallel());
        baz.asyncCall(argFromFoo, 2, this.parallel());
    },
    function thirdStep(err, argFromBar, argFromBaz) {
        if (err) {
            handleError(err);
        }

        /* etc... */
    }
);

如果我不知道更好,我可能猜你会像这样在 async.js 中这样做 (未经测试,认为它是伪代码;我说的是我实际上没有追求的理论上的改变然而)

function thirdStep(argFromBar, argFromBaz) {
    /* etc... */
}

async.waterfall([
    function firstStepNoArgs(callback) {
        foo.asyncCall(callback);
    },
    function secondStep(argFromFoo, callback) {
        async.parallel([
            barResult: function(callback) {
                bar.asyncCall(parameterFromFoo, 1, callback);
            },
            bazResult: function(callback) {
                baz.asyncCall(parameterFromFoo, 2, callback);
            }
        ],
            function(err, result) {
                if (err) {
                    handleError(err);
                } else {
                    thirdStep(result.barResult, result.bazResult);
                }
            }
    }
],
   function(err, result) {
       if (err) {
           handleError(err);
       } else {
           /* no-op? just assume third-step runs? */
       }
   }
);

Step 非常专注和连续,我的小草稿显示它在改编过程中变得混乱。我错过了什么吗?

所以我的问题是:将清晰的 Step 代码转换为 Async.JS 的正确方法是什么?还是我选择了错误的库来升级?我不想让我的代码变得更丑,但我也不想依赖一个看起来有点“死”的库。 :-/

【问题讨论】:

  • 如果你正在切换库,你可能想看看 Q Promise 库,它也被大量使用(它是 npm 中 #11 最依赖的东西)。在 Youtube 上搜索 Q 的主要作者之一 Dominic Denicola 的用户组/会议演讲之一。你至少会得到一个很好的概述,并可以看看它是否更适合你的世界观。我发现它对我很有效。
  • @barry-johnson 非常有趣;看了看。 (显然他的名字拼写为“Domenic”。)如果你很熟悉,你能否顺便给我一个答案,说明它是否可以像阶梯模型一样干净地完成?我不喜欢 async.js 的地方在于它需要为后面的并行项目提前放置回调;它弄乱了代码。一个“Q 视角”可以满足我的期望......!
  • 感谢更正名称。我实际上不想在他的名字中加上任何'e',所以我也总是更正他的姓氏。大声笑 - 我会在今天下午或晚上尝试做一个例子。你能澄清一下this 在你的原始步骤代码中是什么吗?正如它所读到的,我猜它是一个由 step 提供的回调函数,并假设 this.parallel() 再次是一个(专门的)回调,以支持 fork/join 类型的行为。我也会看一下步骤文档。
  • 同时,如果它可能有帮助,我最近answered a question 其中包括一些数据库代码的功能相同的普通回调和启用承诺的版本。我对 Promise 版本进行了大量评论,因此它可能只是一个说明。
  • @barry-johnson 是的,你没看错; this.parallel 是 step 让您开始并行调用的方式,但在当前步骤中的所有调用完成之前,它不会进入下一步。在我的情况下,这就是我想要的……这实际上是关于代码“向下增长”而不是正确增长的基本问题,但我也希望保留序列顺序……!

标签: javascript node.js async.js


【解决方案1】:

根据要求,您使用 Promise 执行的操作。只需粘贴并运行,您就应该明白了。请记住,代码的上半部分是设置模拟函数,以便您更好地了解其工作原理。有人可能会告诉我,作为一个要点,我应该这样做,我也可以这样做。

var Q = require('q');

var foo ={},  bar ={}, baz = {};
//   let's mock up some of your objects with some asynch functions
//   using setTimeout for async completion
foo.asyncCall = function ( cb) {
    setTimeout(function(){ cb(null, 'promises'); },500);
};
bar.asyncCall = function ( arg1, arg2, cb) {
    setTimeout(function(){
        var result = arg1 + ' can be ' + arg2;
        cb(null, result);
    },1200);
};
//  going to add a will-always-fail function for example purposes
bar.asyncFailure = function (arg1, arg2, cb){
    setTimeout(function(){
        cb(new Error(arg1 +' offer decent error handling'), null);
    },2000);    // longer delay - simulate a timeout maybe
};

baz.asyncCall = function ( arg1, arg2, cb) {
    setTimeout(function(){
        var result = arg1 + ' are really ' + arg2;
        cb(null, result);
    },800);
};

//  set up promise-enbaled calls. Q.denodeify is an easy way to deal with any
//  standard node function with a final parameter being an (err,data) callback
//  If these are your own functions, you can also create your own promises, but
//  Q.nodeify is probably the fastest way to adapt existing code.

bar.promiseFailure = Q.denodeify(bar.asyncFailure);
bar.promiseCall = Q.denodeify(bar.asyncCall);
baz.promiseCall = Q.denodeify(baz.asyncCall);

//  this is your wrap up call ('thirdStep' in your code)
function allTogetherNow(arg1, arg2) {
    console.log(arg1 +'\n' + arg2);
};

// now we can have some fun
//  an example that will run to completion normally
//  Q.ninvoke is sort of a 'one-time' denodeify, it invokes a node-style function
//  and returns a promise

function example(){
    Q.ninvoke(foo,'asyncCall')
        .then( function (x) {
            return [bar.promiseCall(x, 'confusing at first'),
                    baz.promiseCall(x, 'awesome after that')]
        })
        .spread(allTogetherNow)
        .fail(function(e){console.log('Had an error')})
        .finally(function(){console.log('Calling no matter what from example')});

};
// sometimes things aren't entirely fun though, and there can be an error
function example2(){
    Q.ninvoke(foo,'asyncCall')
        .then( function (x) {
            return [bar.promiseFailure(x, 'confusing at first'),
                    baz.promiseCall(x, 'awesome after that')]
        })
        .spread(allTogetherNow)
        .fail(function(e){console.log(e)})
        .finally(function(){console.log('Calling no matter what from example2')});
};

example();
example2();

对于那些不想运行它的人,发出的输出是:

promises can be confusing at first
promises are really awesome after that
Calling no matter what from example
[Error: promises offer decent error handling]
Calling no matter what from example2

【讨论】:

  • 感谢 Barry 不仅编写了示例……而且还对其进行了测试!与 Step 代码相比,这肯定有点“一开始很困惑”,但我会研究它并尝试了解其中的含义。我会将这个问题调整为征求替代库,所以如果结果证明它比 Async 更好,我可以接受这个!同时,不知道你的音乐品味,但是..."all the promises we've been giving..." :-)
  • 首先 - 非常欢迎您,感谢您提供歌曲链接 - 一点也不差;很好的夏日驾驶音乐。一些注意事项 - 如果不是立即清楚,spread 方法基本上将来自先前承诺数组的结果转换为传递给传递给 spread 的函数的参数。此外,您可以像这样 (then().fail().then().fail()) 将多个特定的 fail 调用放在一个链中,您也可以在最后添加一个,错误会传播到它,我觉得这很方便。 FWIW,在我刚开始使用 Promise 之前,我对阅读有关 Promise 感到困惑,然后......清晰。
  • 你看起来是个不错的家伙,让我邀请您来和我们聊一些有趣的事情(current top-rated open-source recruitment ad on SO, even)...在Rebol and Red!新想法很适合谈论、承诺或语言或其他方式。 :-)
  • 谢谢。我刚刚弹出链接。耐人寻味。我会在今晚晚些时候检查一下。
  • 我已经把我第一次尝试这个的code review question up。我想我已经有了基本的想法......如果你有任何 cmet 在那里让我知道!
【解决方案2】:

请注意您的回调,这里。回调签名是:

callback(err, arg1, arg2 ...)

因此,如果您的 foo.asyncCall 调用它:

callback(result1, result2)

然后整个 async 将从那时起神秘地失败。正确的成功回调应该以 null 开头,例如

callback(null, result1, result2)

以下是更正后的代码:

function thirdStep(argFromBar, argFromBaz, callback) {
    /* etc... */
    callback(null, result);
}

async.waterfall([
    function firstStepNoArgs(callback) {

        // error callback("failed").  First args is not null means failed
        // in case of error, it just goes straight to function(err, result)

        foo.asyncCall(callback);
    },
    function secondStep(argFromFoo, callback) {

        // argFromFoo come from previous callback (in this case, result1)

        async.parallel([
            barResult: function(callback) {
                bar.asyncCall(parameterFromFoo, 1, callback);
            },
            bazResult: function(callback) {
                baz.asyncCall(parameterFromFoo, 2, callback);
            }
        ],
            function(err, result) {
                if (err) {
                    // in case of error you should do callback(error),
                    // this callback is from secondStep(argFromFoo, callback).

                    // this will pass to final function(err, result).

                    handleError(err);

                } else {

                    // you need to do callback(null) inside thirdStep
                    // if callback is not called, the waterfall won't complete

                    thirdStep(result.barResult, result.bazResult, callback);
                }
            }
    }
],
   function(err, result) {
       if (err) {
           handleError(err);
       } else {

           // everything is executed correctly, 
           // if any step failed it will gone to err.

       }
   }
);

【讨论】:

  • +1 Wayne,用于指出参数更正。我想我的问题是我是否可以使用 Async 使一些东西像原来的那样更清晰。我是否必须通过将第三步放在第二步上方来打破我的瀑布,只是因为它是平行的? :-/
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-07-23
  • 2019-09-06
  • 2016-10-11
  • 1970-01-01
  • 2017-09-10
  • 2016-10-21
  • 2018-11-14
相关资源
最近更新 更多