Выполнение обратных вызовов в последовательном порядке без использования обещаний - PullRequest
1 голос
/ 06 июня 2019

Я пытаюсь выполнить следующий массив (избегая 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 ]

2 голосов
/ 06 июня 2019

Пусть обратный вызов .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 голос
/ 07 июня 2019

Ваши обратные вызовы никогда не передают аргументы, 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'

Для дополнительной дозы функционального программирования -

Приведенный выше редуктор основанна фундаментальной структуре данных под названием Продолжение .Если мы извлечем его, мы увидим, что 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))
1 голос
/ 06 июня 2019
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() в консоль, затем выдает исключение, потому что undefined не является функцией.

...