Подтвердить что ты не робот

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

Я видел цепочку произвольного числа promises в 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", которая абстрагирует рекурсию. Какой лучший способ?

4b9b3361

Ответ 1

Там есть хороший чистый способ: [].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 выполняет итерацию через массив, передавая возвращаемое значение предыдущей итерации. В этом случае вы возвращаете promises, и поэтому каждый раз, когда вы цепляете then. Вы предоставляете первоначальное обещание (как вы это делали с q.resolve("start")), чтобы отбросить ситуацию.

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

Ответ 2

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

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.

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

Ответ 3

Вот понятие конечного автомата, определенного с помощью 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.
});

Ответ 4

Я предлагаю другие решения, которые мне легче понять. Вы делаете то же самое, что и при непосредственном соединении promises: 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) {
}

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