异步方法的顺序调用

异步方法的顺序调用

问题描述:

我有一个要在方法中调用的方法的列表,如下所示:

I have a list of methods I am calling in a method, as follows:

this.doOneThing();
someOtherObject.doASecondThing();
this.doSomethingElse();

如果这是同步的,则将它们一个接一个地执行,这是必需的.但是现在我将someOtherObject.doASecondThing()设为异步,并且我也可能将doOneThing设为异步.我可以使用回调并从回调内部调用that.doSomethingElse:

When this is synchronous, they are executed one after the other, which is required. But now I have someOtherObject.doASecondThing() as asynchronous, and I might turn doOneThing as async too. I could use a callback and call that.doSomethingElse from inside the callback:

var that = this;
this.doOneThing( function () { 
                    someOtherObject.doASecondThing(function () {
                        that.doSomethingElse();
                    });
                  });

但是,由于序列不断增长,因此回调之间互相调用似乎有些混乱,由于某种原因,它使序列看起来不像以前那么明显,并且缩进可能会随着在其中调用的方法数量的增加而增加.顺序.

However, because the sequence is growing, it seems a little messy to have callbacks call each other, for some reason it makes the sequence not look as obvious as before, and the indentation might grow with the number of methods called in the sequence.

有没有办法使它看起来更好?我也可以使用观察者模式,但我认为它也不会使事情变得很明显.

Is there a way to make this look better? I could use the observer pattern also but it doesn't make things very obvious either, in my opinion.

谢谢

继续,以及为什么导致回调意大利面条

在回调中进行写操作会迫使您类似于连续传递样式"(CPS)(这是一种非常强大但又很困难的技术)进行编写.它代表了控制的完全反转,从字面上将计算颠倒"了. CPS使您的代码结构明确反映程序的控制流(有时是好事,有时是坏事).实际上,您是在使用匿名函数显式地记录堆栈.

Continuations, and why they're causing callback spaghetti

Writing in callbacks forces you to write in sometime akin to "continuation-passing style" (CPS), an extremely powerful but difficult technique. It represents a total inversion of control, literally turning a computation "upside-down". CPS makes your code's structure explicitly reflect the control flow of your program (sometimes a good thing, sometimes a bad thing). In effect, you are explicitly writing down the stack out of anonymous functions.

作为理解此答案的先决条件,您可能会发现这很有用:

As a prerequisite for understanding this answer, you may find this useful:

http://matt.might.net/articles/by -example-continuation-passing-style/

例如,这就是您正在做的事情:

For example this is what you are doing:

function thrice(x, ret) {
    ret(x*3)
}
function twice(y, ret) {
    ret(y*2)
}
function plus(x,y, ret) {
    ret(x+y)
}

function threeXPlusTwoY(x,y, ret) {
    // STEP#1
    thrice(x,                 // Take the result of thrice(x)...
        function(r1) {        // ...and call that r1.
            // STEP#2
            twice(y,            // Take the result of twice(y)...
                function(r2) {  // ...and call that r2.
                    // STEP#3
                    plus(r1,r2,   // Take r1+r2...
                        ret       // ...then do what we were going to do.
                    )
                }
            )
        }
    )
}

threeXPlusTwoY(5,1, alert);  //17

正如您所抱怨的那样,这使代码缩进了很多,因为闭包是捕获此堆栈的自然方法.

As you've complained about, this makes for fairly indented code, because closures are the natural way to capture this stack.

取消CPS缩进的一种方法是像在Haskell中那样单调"地编写.我们该怎么做?在javascript中实现monad的一种好方法是使用点链表示法,类似于jQuery. (请参见 http://importantshock.wordpress.com/2009/01/18/jquery-is-a-monad/进行有趣的转移.)或者我们可以使用反射.

One way to unindent CPS is to write "monadically" like in Haskell. How would we do that? One nice way of implementing monads in javascript is with the dot-chaining notation, similar to jQuery. (See http://importantshock.wordpress.com/2009/01/18/jquery-is-a-monad/ for an amusing diversion.) Or we can use reflection.

但是首先我们需要一种写下管道"的方法,然后我们可以找到一种将其抽象化的方法.可悲的是,用javascript编写通用的monad语法有点困难,因此我将使用列表来表示计算.

But first we need a way to "write down the plumbing", and THEN we can find a way abstract it away. Tragically it's sort of hard to write a generic monad syntax in javascript, so I will use lists to represent computations.

 

// switching this up a bit:
// it's now 3x+2x so we have a diamond-shaped dependency graph

// OUR NEW CODE
var _x = 0;
var steps = [
    [0,  function(ret){ret(5)},[]],  //step0:
    [1,  thrice,[_x]],               //step1: thrice(x)
    [2,  twice,[_x]],                //step2: twice(x)
    [3,  plus,[1, 2]]                //step3: steps[1]+steps[2] *
]
threeXPlusTwoX = generateComputation(steps);

//*this may be left ambiguous, but in this case we will choose steps1 then step2
// via the order in the array

有点丑陋.但是我们可以使这种UNINDENTED代码"起作用.我们可能会担心稍后使它更漂亮(在上一节中).在这里,我们的目的是写下所有必要信息".我们希望有一种简单的方法来编写每个行",以及可以在其中写入内容的上下文.

That's kinda ugly. But we can make this UNINDENTED "code" work. We can worry about making it prettier later (in the last section). Here our purpose was to write down all the "necessary information". We'd like an easy way to write each "line", along with a context we can write them in.

现在,我们实现一个generateComputation,该generateComputation生成一些嵌套的匿名函数,如果执行它们,它们将按顺序执行上述步骤.这样的实现将如下所示:

Now we implement a generateComputation which generates some nested anonymous functions which would perform the above steps in-order if we executed it. This is what such an implementation would look like:

function generateComputation(steps) {
    /*
    * Convert {{steps}} object into a function(ret), 
    * which when called will perform the steps in order.
    * This function will call ret(_) on the results of the last step.
    */
    function computation(ret) {
        var stepResults = [];

        var nestedFunctions = steps.reduceRight(
            function(laterFuture, step) {
                var i            = step[0];  // e.g. step #3
                var stepFunction = step[1];  // e.g. func: plus
                var stepArgs     = step[2];  // e.g. args: 1,2

                console.log(i, laterFuture);
                return function(returned) {
                    if (i>0)
                        stepResults.push(returned);
                    var evalledStepArgs = stepArgs.map(function(s){return stepResults[s]});
                    console.log({i:i, returned:returned, stepResults:stepResults, evalledStepArgs:evalledStepArgs, stepFunction:stepFunction});
                    stepFunction.apply(this, evalledStepArgs.concat(laterFuture));
                }
            },
            ret
        );

        nestedFunctions();
    }
    return computation;
}

演示:

threeXPlusTwoX = generateComputation(steps)(alert);  // alerts 25

旁注:reduceRight语义意味着右侧的步骤将更深地嵌套在函数中(将来会更进一步).对于不熟悉的[1,2,3].reduce(f(_,_), x) --> f(f(f(0,1), 2), 3)reduceRight(由于设计考虑不佳的人)的FYI实际上等同于[1.2.3].reversed().reduce(...)

sidenote: reduceRight semantics implies the steps on the right will be more deeply nested in functions (further in the future). FYI for those not familiar, [1,2,3].reduce(f(_,_), x) --> f(f(f(0,1), 2), 3), and reduceRight (due to poor design considerations) is actually equivalent to [1.2.3].reversed().reduce(...)

上面,generateComputation创建了一堆嵌套函数,在准备中将它们彼此包装在一起,并在用...(alert)求值时,将它们一个一个地剥离以供计算.

Above, generateComputation made a bunch of nested functions, wrapping them in one another in preparation, and when evaluated with ...(alert), unpeeled them one-by-one to feed into the computation.

边注:我们必须使用hack,因为在前面的示例中,我们使用了闭包和变量名来实现CPS. Javascript不允许充分的反射来执行此操作,而无需诉诸于制作字符串并eval对其进行处理(点击),因此我们暂时避开了功能样式,并选择了对跟踪所有参数的对象进行变异.因此,以上内容更精确地复制了以下内容:

sidenote: We have to use a hack because in the previous example, we used closures and variable names to implement CPS. Javascript does not allow sufficient reflection to do this without resorting to crafting a string and evaling it (ick), so we eschew functional style temporarily and opt for mutating an object that keeps track of all parameters. Thus the above more closely replicates the following:

var x = 5;
function _x(ret) {
    ret(x);
}

function thrice(x, ret) {
    ret(x*3)
}
function twice(y, ret) {
    ret(y*2)
}
function plus(x,y, ret) {
    ret(x+y)
}

function threeXPlusTwoY(x,y, ret) {
    results = []
    _x(
        return function(x) {
            results[0] = x;

            thrice(x,                 // Take the result of thrice(x)...
                function(r1) {        // ...and call that r1.
                    results[1] = r1;

                    twice(y,            // Take the result of twice(y)...
                        function(r2) {  // ...and call that r2.
                            results[2] = r2;

                            plus(results[1],results[2],   // Take r1+r2...
                                ret       // ...then do what we were going to do.
                            )
                        }
                    )
                }
            )

        }
    )
}


理想语法

但是我们仍然想以一种明智的方式编写函数.在保持我们的理智的同时,我们如何理想如何编写代码以利用CPS?文献中有很多内容(例如,Scala的shiftreset运算符只是这样做的许多方法之一),但是出于理智的考虑,让我们找到一种为常规CPS制作语法糖的方法.有一些可能的方法可以做到:


Ideal syntax

But we still want to write functions in a sane way. How would we ideally like to write our code to take advantage of CPS, but while retaining our sanity? There are numerous takes in the literature (for example, Scala's shift and reset operators are just one of the many ways to do so), but for sanity's sake, let's just find a way to make syntactic sugar for regular CPS. There are some possible ways to do it:

 

// "bad"
var _x = 0;
var steps = [
    [0,  function(ret){ret(5)},[]],  //step0:
    [1,  thrice,[_x]],               //step1: thrice(x)
    [2,  twice,[_x]],                //step2: twice(x)
    [3,  plus,[1, 2]]                //step3: steps[1]+steps[2] *
]
threeXPlusTwoX = generateComputation(steps);

...成为...

  • 如果回调位于一个链中,则我们可以轻松地将回调馈入下一个,而不必担心命名.这些函数只有一个参数:回调参数. (如果没有,则可以在最后一行中按如下所示使用该函数.)在这里,我们可以使用jQuery样式的点链.

 

// SYNTAX WITH A SIMPLE CHAIN
// ((2*X) + 2)
twiceXPlusTwo = callbackChain()
    .then(prompt)
    .then(twice)
    .then(function(returned){return plus(returned,2)});  //curried

twiceXPlusTwo(alert);

  • 如果回调形成一个依赖关系树,我们也可以摆脱jQuery风格的点链,但这将破坏为CPS创建monadic语法的目的,即平整嵌套函数.因此,我们在这里不做详细介绍.

    • If the callbacks form a dependency tree, we can also get away with jQuery-style dot-chains, but this would defeat the purpose of creating a monadic syntax for CPS, which is to flatten the nested functions. Thus we won't go into detail here.

      如果回调形成依赖关系非循环图(例如,2*x+3*x,其中x被使用两次),我们将需要一种方法来命名某些回调的中间结果.这就是它变得有趣的地方.我们的目标是尝试模仿 http://en.wikibooks.org/wiki/Haskell的语法/Continuation_passing_style 及其do标记,可解包"和再包入"功能进入和退出CPS.不幸的是,[1, thrice,[_x]]语法是我们最容易接近的语法(甚至不是很接近).您可以使用另一种语言编写代码,然后编译为javascript,也可以使用eval(不祥的音乐排队).有点矫kill过正.替代方案必须使用字符串,例如:

      If the callbacks form a dependency acyclic graph (for example, 2*x+3*x, where x is used twice) we would need a way to name the intermediate results of some callbacks. This is where it gets interesting. Our goal is to try to mimic the syntax at http://en.wikibooks.org/wiki/Haskell/Continuation_passing_style with its do-notation which "unwraps" and "rewraps" functions into and out of CPS. Unfortunately the [1, thrice,[_x]] syntax was the closest we could easily get to that (and not even close). You could code in another language and compile to javascript, or using eval (queue the ominous music). A bit overkill. Alternatives would have to use strings, such as:

       

      // SUPER-NICE SYNTAX
      // (3X + 2X)
      thriceXPlusTwiceX = CPS({
          leftPart: thrice('x'),
          rightPart: twice('x'),
          result: plus('leftPart', 'rightPart')
      })
      

      您只需对我描述的generateComputation进行一些调整即可.首先,您将其改编为使用逻辑名('leftPart'等)而不是数字.然后,您使函数实际上是惰性对象,其行为类似于:

      You can do this with only a few tweaks to the generateComputation I described. First you adapt it to use logical names ('leftPart', etc.) rather than numbers. Then you make your functions actually lazy objects which behave like:

      thrice(x).toListForm() == [<real thrice function>, ['x']]
      or
      thrice(x).toCPS()(5, alert)  // alerts 15
      or
      thrice.toNonCPS()(5) == 15
      

      (您可以使用某种装饰器以自动化方式执行此操作,而不是手动进行.)

      (You would do this in an automated way with some kind of decorator, not manually.)

      边注:您的所有回调函数应遵循相同的协议,有关回调参数的位置.例如,如果您的函数以myFunction(callback, arg0, arg1, ...)myFunction(arg0, arg1, ..., callback)开头,它们可能并不完全兼容,尽管如果它们可能不兼容,您可以进行javascript反射hack来查看该函数的源代码并对其进行正则表达式处理,然后因此不必担心.

      sidenote: All your callback functions should follow the same protocol about where the callback parameter is. For example if your functions begin with myFunction(callback, arg0, arg1, ...) or myFunction(arg0, arg1, ..., callback) they might not be trivially compatible, though if they aren't presumably you could do a javascript reflection hack to look at the source code of the function and regex it out, and thus not have to worry about it.

      为什么要经历所有麻烦?这使您可以混入setTimeoutprompt以及ajax请求,而不会遇到缩进地狱"的麻烦.您还将获得很多其他好处(例如,能够编写10行非确定性搜索数独解算器,以及实现任意控制流运算符),我在这里不会赘述.

      Why go through all that trouble? This allows you to mix in setTimeouts and prompts and ajax requests without suffering from "indent hell". You also get a whole bunch of other benefits (like being able to write a 10-line nondeterministic-search sudoku solver, and implementing arbitrary control-flow operators) which I will not go into here.