Как связать переменное количество обещаний в 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. Или определить новую функцию под названием «цикл», которая абстрагирует рекурсию. Что лучше?


person Cheeso    schedule 20.07.2013    source источник
comment
Кажется, это не переменное количество обещаний, а установленное количество, зависящее от длины массива. Бывают случаи, когда у вас действительно есть номер переменной, и я не уверен, что метод reduce работает для них.   -  person hippietrail    schedule 07.07.2016
comment
github.com/kriskowal/q#sequences будет официальной ссылкой.   -  person Aides    schedule 06.02.2017


Ответы (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")), чтобы начать работу.

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

person Stuart K    schedule 20.07.2013
comment
милая! и очень удобно! - person frequent; 04.05.2014
comment
Что происходит в случае отклонения обещания? Или, по крайней мере, как бы мы поступили с этим делом? - person naivedeveloper; 19.03.2015
comment
@naivedeveloper Если шаг отклоняется, следующие шаги не будут выполнены, и chain также будет отклонен, поэтому вы можете просто поймать этот случай с помощью chain.catch. - person Stuart K; 21.03.2015
comment
Очень красивое решение. Спасибо! - person datUser; 06.10.2015
comment
Это идеально и растопило мой мозг, пока я не увидел, как он работает. Тогда это имело смысл. - person charliebeckwith; 08.11.2016
comment
Как сказал @manu, сначала это довольно умопомрачительно, но, черт возьми, это круто, когда вы обдумываете это. Спас меня от хитрой проблемы на работе. Спасибо! - person pulse0ne; 10.03.2017

Мне так больше нравится:

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.

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

person Cheeso    schedule 20.07.2013
comment
Вероятно, более нормально построить цепочку .then() в цикле, как в вашем первом примере, чем идти рекурсивно. Какой бы способ вы ни выбрали, может быть важно не уничтожить исходный массив. Если это так, то вы можете заставить обещания (и их обратные вызовы) работать с увеличивающимся значением индекса, который работает с исходным незапятнанным массивом. - person Beetroot-Beetroot; 20.07.2013

Вот концепция конечного автомата, определенная с помощью 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.
});
person yuxhuang    schedule 20.07.2013
comment
Для меня это имеет гораздо больше смысла, чем другие решения для цикла while. Также проще адаптироваться к другим библиотекам Promise. - person Seth; 26.11.2014

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

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

person Marcel Böttcher    schedule 03.07.2015