Executing callbacks in sequential order without using Promises

前端 未结 3 1289
终归单人心
终归单人心 2020-12-07 05:41

I am trying to execute following array (avoid callbackHell) of functions in a sequential order implementing function runCallbacksInSequence (I need to implement

相关标签:
3条回答
  • 2020-12-07 06:08

    Have the .reduce callback be a higher-order function, which, when called, calls the next function in the chain with the callback. At the end, you'll have a function chain that will start by calling the first function, then the second, etc:

    function first(cb) {
      console.log('first()');
      cb();
    }
    function second(cb) {
      console.log('second()');
      cb();
    }
    function third(cb) {
      console.log('third()');
      cb();
    }
    function last() {
      console.log('last()');
    }
    
    let fns = [first, second, third, last];
    
    function runCallbacksInSequence(fns, cb) {
      const chainedFns = fns.reduceRight((acc, f) => () => f(acc), cb);
      return chainedFns();
    }
    
    runCallbacksInSequence(fns);

    If you wanted the runCallbacksInSequence to accept another callback to run at the end of all of them, then:

    function first(cb) {
      console.log('first()');
      cb();
    }
    function second(cb) {
      console.log('second()');
      cb();
    }
    function third(cb) {
      console.log('third()');
      cb();
    }
    function last(cb) {
      console.log('last()');
      cb();
    }
    
    let fns = [first, second, third, last];
    
    function runCallbacksInSequence(fns, cb) {
      const chainedFns = fns.reduceRight((acc, f) => () => f(acc), cb);
      return chainedFns();
    }
    
    runCallbacksInSequence(fns, () => console.log('outer call'));

    0 讨论(0)
  • 2020-12-07 06:14
    fns.reduceRight((acc, f) => f(acc), cb)
    

    runs

    [first, second, third, last].reduceRight((acc, f) => f(acc), second)
    

    which turns into

    ((acc, f) => f(acc))(
        ((acc, f) => f(acc))(
            ((acc, f) => f(acc))(
                 ((acc, f) => f(acc))(
                     second,
                     last
                 ),
                 third
            ),
            second
        ),
        first
    )
    

    (because that's what reduceRight does).

    The first thing to run is the innermost call,

     ((acc, f) => f(acc))(
         second,
         last
     )
    

    This becomes

    last(second)
    

    which (by the definition of last) is equivalent to

    (function () { console.log('last()'); })(second)
    

    This expression ignores second, writes last() to the console, and returns undefined.

    This leaves our expression as

    ((acc, f) => f(acc))(
        ((acc, f) => f(acc))(
            ((acc, f) => f(acc))(
                 undefined,
                 third
            ),
            second
        ),
        first
    )
    

    The next innermost call is

    ((acc, f) => f(acc))(
         undefined,
         third
    )
    

    which turns into

    third(undefined)
    

    By the definition of third this is equivalent to

    (function (cb) {
        console.log('third()');
        cb();
    })(undefined)
    

    which in turn executes

    console.log('third()');
    undefined();
    

    This writes third() to the console, then throws an exception because undefined is not a function.

    0 讨论(0)
  • 2020-12-07 06:32

    Your callbacks never pass arguments, cb(). In a real program, you will likely want to get a result back. The callback is meant to receive some sort of message - ie, what are you calling back to say? In this program, we'll send some messages out and make sure all of them get passed to the final callback -

    function first(cb) {
      console.log('first()')
      cb(1) // return some result
    }
    
    function second(cb) {
      console.log('second()')
      cb(2) // return some result
    }
    
    function third(cb) {
      console.log('third()')
      cb(3) // return some result
    }
    
    function last(cb) {
      console.log('last()')
      cb('last') // return some result
    }
    
    function runCallbacksInSequence(fns, cb) {
      fns.reduce
        ( (r, f) => k => r(acc => f(x => k([ ...acc, x ])))
        , k => k([])
        )
        (cb)
    }
    
    const fns =
      [ first, second, third, last ]
    
    runCallbacksInSequence(fns, results => {
      console.log("-- DONE --")
      console.log(...results)
    })

    The output is -

    first()
    second()
    third()
    last()
    -- DONE --
    1 2 3 'last'
    

    For an extra dose of functional programming -

    The reducer above is based on a fundamental data structure called Continuation. If we extract it, we can see what runCallbacksInSequence is doing more clearly -

    function append (a = [], x = null) {
      return a.concat([ x ])     // basic append operation
    }
    
    function runCallbacksInSequence(fns, cb) {
      Cont.run
        ( fns.reduce             // in the context of Cont ...
            ( Cont.lift2(append) // reduce using append
            , Cont.of([])        // init with empty array
            )
        , cb
        )
    }
    

    Here's Cont -

    const Cont =
      { of: x =>
          k => k (x)
      , lift2: f => (mx, my) =>
          k => mx (x => my (y => k (f (x, y))))
      , run: (c, k) =>
          c (k)
      }
    

    Expand the snippet below to see the result in your own browser -

    function first(cb) {
      console.log('first()')
      cb(1) // return some result
    }
    
    function second(cb) {
      console.log('second()')
      cb(2) // return some result
    }
    
    function third(cb) {
      console.log('third()')
      cb(3) // return some result
    }
    
    function last(cb) {
      console.log('last()')
      cb('last') // return some result
    }
    
    const Cont =
      { of: x =>
          k => k (x)
      , lift2: f => (mx, my) =>
          k => mx (x => my (y => k (f (x, y))))
      , run: (c, k) =>
          c (k)
      }
    
    function append (a = [], x = null) {
      return a.concat([ x ])
    }
    
    function runCallbacksInSequence(fns, cb) {
      Cont.run
        ( fns.reduce
            ( Cont.lift2(append)
            , Cont.of([])
            )
        , cb
        )
    }
    
    const fns =
      [ first, second, third, last ]
    
    runCallbacksInSequence(fns, results => {
      console.log("-- DONE --")
      console.log(...results)
    })


    Using reduce is not the only way to express this kind of program. Programming is all about inventing your own convenience. What if we could have an intuitive, magical function like $ below? We could start with some value and then just chain as many steps necessary -

    $ ([])
      (andAppend(first))
      (andAppend(second))
      (andAppend(second))
      (andAppend(third))
      (andAppend(third))
      (andAppend(third))
      (andAppend(last))
      (x => console.log ("done", x))
    
    // first()
    // second()
    // second()
    // third() 
    // third()
    // third()
    // last()
    // "done" [ 1, 2, 2, 3, 3, 3, "last" ]
    

    Any simple function can go in the sequence -

    function progress(p) {
      console.log("progress:", p)
      return p
    }
    
    $ ([])
      (andAppend(first))
      (andAppend(second))
      (progress)
      (andAppend(third))
      (andAppend(last))
      (x => console.log ("done", x))
    
    // first()
    // second()
    // progress: [ 1, 2 ]
    // third()
    // last()
    // "done" [ 1, 2, 3, "last" ]
    

    This appears to be a very intuitive way to work with our asynchronous functions. We just have to implement $ now. How hard could it be?

    const $ = x =>
      k => $(Promise.resolve(x).then(k))
    

    And now we implement andAppend -

    function andAppend(f) {
      return acc =>
        new Promise(r =>
          f(x => r([ ...acc, x ]))
        )
    }
    

    Expand the snippet below to see it working your browser -

    function first(cb) {
      console.log('first()')
      cb(1)
    }
    
    function second(cb) {
      console.log('second()')
      cb(2)
    }
    
    function third(cb) {
      console.log('third()')
      cb(3)
    }
    
    function last(cb) {
      console.log('last()')
      cb('last')
    }
    
    function andAppend(f) {
      return acc =>
        new Promise(r =>
          f(x => r([ ...acc, x ]))
        )
    }
    
    function progress(p) {
      console.log("progress:", p)
      return p
    }
    
    const $ = x =>
      k => $(Promise.resolve(x).then(k))
    
    $ ([])
      (andAppend(first))
      (andAppend(second))
      (progress)
      (andAppend(third))
      (andAppend(last))
      (x => console.log ("done", x))

    0 讨论(0)
提交回复
热议问题