按顺序执行回调而不使用Promises

时间:2019-06-06 06:26:15

标签: javascript callback functional-programming

我正在尝试按顺序执行函数的以下数组(避免使用callbackHell),以实现函数runCallbacksInSequence(我需要实现自己的函数以了解回调如何工作并避免使用Async.js)。这是我到目前为止所拥有的。我不太了解回调如何工作,这就是为什么我要进行此练习。如果您有任何想法,请告诉我我做错了什么以及如何解决。

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) {
  return fns.reduceRight((acc, f) => f(acc), cb);
}

runCallbacksInSequence(fns, second);

callbackHell

// first(function() {
//   third(function() {
//     second(function() {
//       last();
//     });
//   });
// });

UPD

    const cache = {};

    function runCallbacksInSequence(fns, cb) {
      fns.reduce(
        function(r, f) {
          return function(k) {
            return r(function() {
              if (cache[f]) {
                return;
                // f(function(e, x) {
                //   e ? cb(e) : k(x);
                // });
              } else {
                cache[f] = f;
                return f(function(e, x) {
                  return e ? cb(e) : k(x);
                });
              }
            });
          };
        },
        function(k) {
          return k();
        }
      )(function(r) {
        return cb(null, r);
      });
    }

3 个答案:

答案 0 :(得分:2)

.reduce回调是一个高阶函数,该函数在被调用时将使用该回调调用链中的下一个函数。最后,您将具有一个功能链,该功能链将首先调用第一个函数,然后调用第二个函数,等等:

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

如果您希望runCallbacksInSequence接受另一个回调在所有回调的末尾运行,则:

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

答案 1 :(得分:1)

Vue.js

运行

fns.reduceRight((acc, f) => f(acc), cb)

变成

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

(因为((acc, f) => f(acc))( ((acc, f) => f(acc))( ((acc, f) => f(acc))( ((acc, f) => f(acc))( second, last ), third ), second ), first ) 就是这样做的。)

首先运行的是最内部的调用

reduceRight

这变成

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

(根据last(second) 的定义,它等同于

last

此表达式忽略(function () { console.log('last()'); })(second) ,将second写入控制台,然后返回last()

这使我们的表情为

undefined

下一个最里面的呼叫是

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

变成

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

根据third(undefined) 的定义,这等同于

third

依次执行

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

这会将console.log('third()'); undefined(); 写到控制台,然后抛出异常,因为third()不是函数。

答案 2 :(得分:1)

您的回调函数永远不会传递参数cb()。在实际程序中,您可能希望获得结果。回调旨在接收某种消息-即,您要回叫?在此程序中,我们将发送一些消息,并确保将所有消息都传递给最终的回调-

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

输出为-

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

需要额外的功能编程-

上面的reducer基于称为 Continuation 的基本数据结构。如果提取它,我们可以更清楚地看到runCallbacksInSequence在做什么-

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

这里是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)
  }

展开以下代码段,以在您自己的浏览器中查看结果-

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


使用reduce并不是表达此类程序的唯一方法。编程就是为了创造自己的便利。如果我们可以拥有下面$这样的直观,神奇的功能,该怎么办?我们可以从一些价值开始,然后链出许多必要的步骤-

$ ([])
  (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" ]

任何简单的函数都可以按顺序执行-

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" ]

这似乎是使用异步功能的一种非常直观的方法。我们现在只需要实现$。有多难?

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

现在我们实现andAppend-

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

展开下面的代码片段,以查看它在您的浏览器中的作用-

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