Как связать переменное число обещаний в Q, по порядку?

Я видел цепочку произвольного числа обещаний в Q; мой вопрос другой.

Как я могу сделать переменное количество вызовов, каждый из которых возвращает асинхронно, по порядку?
Сценарий представляет собой набор HTTP-запросов, количество и тип которых определяются по результатам первого HTTP-запроса.

Я хотел бы сделать это просто.

Я также видел этот ответ, который предполагает что-то вроде этого:

var q = require('q'),
    itemsToProcess =  ["one", "two", "three", "four", "five"];

function getDeferredResult(prevResult) {
  return (function (someResult) {
    var deferred = q.defer();
    // any async function (setTimeout for now will do, $.ajax() later)
    setTimeout(function () {
      var nextResult = (someResult || "Initial_Blank_Value ") + ".." + itemsToProcess[0];
      itemsToProcess = itemsToProcess.splice(1);
      console.log("tick", nextResult, "Array:", itemsToProcess);
      deferred.resolve(nextResult);
    }, 600);

    return deferred.promise;
  }(prevResult));
}

var chain = q.resolve("start");
for (var i = itemsToProcess.length; i > 0; i--) {
    chain = chain.then(getDeferredResult);
}

... но, кажется, неловко перебирать itemsToProcess таким образом. Или определить новую функцию с именем "loop", которая абстрагирует рекурсию. Какой способ лучше?

4 ответа

Решение

Там есть хороший, чистый способ [].reduce,

var chain = itemsToProcess.reduce(function (previous, item) {
    return previous.then(function (previousValue) {
        // do what you want with previous value
        // return your async operation
        return Q.delay(100);
    })
}, Q.resolve(/* set the first "previousValue" here */));

chain.then(function (lastResult) {
    // ...
});

reduce перебирает массив, передавая возвращенное значение предыдущей итерации. В этом случае вы возвращаете обещания, и поэтому каждый раз, когда вы соединяете then, Вы предоставляете первоначальное обещание (как вы сделали с q.resolve("start")), чтобы начать вещи.

Поначалу может потребоваться некоторое время, чтобы обдумать, что здесь происходит, но если вы потратите немного времени на его проработку, то его легко использовать в любом месте, без необходимости устанавливать какие-либо механизмы.

Мне нравится этот способ лучше:

var q = require('q'),
    itemsToProcess =  ["one", "two", "three", "four", "five"];

function getDeferredResult(a) {
  return (function (items) {
    var deferred;

    // end
    if (items.length === 0) {
      return q.resolve(true);
    }

    deferred = q.defer();

    // any async function (setTimeout for now will do, $.ajax() later)
    setTimeout(function () {
      var a = items[0];
      console.log(a);
      // pop one item off the array of workitems
      deferred.resolve(items.splice(1));
    }, 600);

    return deferred.promise.then(getDeferredResult);
  }(a));
}

q.resolve(itemsToProcess)
  .then(getDeferredResult);

Ключ здесь, чтобы позвонить .then() на deferred.promise с объединенной версией массива рабочих элементов. это then запускается после разрешения первоначального отложенного обещания, которое находится в fn для setTimeout. В более реалистичном сценарии отложенное обещание будет разрешено в обратном вызове http-клиента.

Начальный q.resolve(itemsToProcess) отбрасывает вещи, передавая рабочие элементы первому вызову работы fn.

Я добавил это в надежде, что это поможет другим.

Я предлагаю другие решения, которые кажутся мне более понятными. Вы делаете то же самое, что и при создании цепочки обещаний напрямую: promise.then(doSomethingFunction).then(doAnotherThingFunction);

Если мы поместим это в цикл, мы получим это:

var chain = Q.when();
for(...) {
  chain = chain.then(functionToCall.bind(this, arg1, arg2));
};
chain.then(function() {
    console.log("whole chain resolved");
});


var functionToCall = function(arg1, arg2, resultFromPreviousPromise) {
}

Мы используем функцию curry для использования нескольких аргументов. В нашем примереfunctionToCall.bind(this, arg1, arg2) вернет функцию с одним аргументом: functionToCall(resultFromPreviousPromise)Вам не нужно использовать результат предыдущего обещания.

Вот концепция конечного автомата, определенного с Q,

Предположим, у вас определена функция HTTP, поэтому она возвращает Q объект обещания:

var Q_http = function (url, options) {
  return Q.when($.ajax(url, options));
}

Вы можете определить рекурсивную функцию nextState следующим образом:

var states = [...]; // an array of states in the system.

// this is a state machine to control what url to get data from
// at the current state 
function nextState(current) {
  if (is_terminal_state(current))
    return Q(true);

  return Q_http(current.url, current.data).then(function (result) {
    var next = process(current, result);
    return nextState(next);
  });
}

куда function process(current, result) это функция, чтобы узнать, каким будет следующий шаг в соответствии с current состояние и result из HTTP-вызова.

Когда вы используете его, используйте его так:

nextState(initial).then(function () {
  // all requests are successful.
}, function (reason) {
  // for some unexpected reason the request sequence fails in the middle.
});
Другие вопросы по тегам