Цепочка Promises рекурсивно
Я работаю над простым приложением Windows 8, в котором мне нужно получить набор данных с веб-сайта. Я использую WinJS.xhr() для извлечения этих данных, которые возвращают Promise. Затем я передаю обратный вызов в этот метод Promise.then(), который поставляет мой обратный вызов с возвращаемым значением из асинхронного вызова. Метод .then() возвращает другое обещание, предоставляя ему значение, возвращаемое моим обратным вызовом. Основная структура такого запроса будет следующей:
WinJS.xhr({ url: "http://www.example.com/" }).then(
function callback( result_from_xhr )
{
//do stuff
return some_value;
}).then(
function secondcallback( some_value )
{
//do stuff
});
Однако в моей ситуации мне может потребоваться дополнительные запросы для данных в зависимости от данных, возвращаемых первым запросом, и, возможно, больше запросов в зависимости от данных THAT... и т.д., рекурсивно.
Мне нужен способ закодировать это так, что final.then() не будет выполняться до тех пор, пока ВСЕ рекурсии не будут завершены, аналогично этому:
function recurse() {
return WinJS.xhr({ url: "http://www.example.com/" }).then(
function callback( result_from_xhr )
{
if( result_from_xhr == something )
{
recurse();
}
});
}
recurse().then(
function final()
{
//finishing code
});
Проблема состоит в том, что, конечно, код завершения вызывается, как только завершается первый уровень рекурсии. Мне нужно каким-то образом вложить новое обещание и старое обещание в рамках обратного вызова.
Надеюсь, мой вопрос достаточно ясен, я действительно не уверен, как это объяснить, и, откровенно говоря, идея асинхронного рекурсивного кода заставляет мою голову болеть.
Ответы
Ответ 1
Что бы я хотел сделать, это создать совершенно новое, автономное обещание, которое вы можете выполнить вручную, и вернуть его из функции recurse(). Затем, когда вы натолкнулись на то, что знаете, что вы сделали асинхронную работу, выполните это обещание.
Promise.join работает, когда у вас есть известный набор promises - вам нужен весь массив promises, доступный до вызова соединения. Если бы я выполнил исходный вопрос, у вас есть переменное число promises, более вероятно, появившееся как часть работы async. Присоединиться не является правильным инструментом в этих обстоятельствах.
Итак, как это выглядит? Что-то вроде этого:
function doSomethingAsync() {
return new WinJS.Promise(function (resolve, reject) {
function recurse() {
WinJS.xhr({ url: "http://www.example.com/" })
.then(function onResult(result_from_xhr) {
if (result_from_xhr === something) {
recurse();
} else {
// Done with processing, trigger the final promise
resolve(whateverValue);
},
function onError(err) {
// Fail everything if one of the requests fails, may not be
// the right thing depending on your requirements
reject(err);
});
}
// Kick off the async work
recurse();
});
}
doSomethingAsync().then(
function final()
{
//finishing code
});
Я перегруппировался там, где происходит рекурсия, чтобы мы не воссоздавали новое обещание каждый раз, поэтому вложенная функция recurse() вместо того, чтобы иметь ее на внешнем уровне.
Ответ 2
Я решил эту проблему, возможно, по-другому (я думаю, это та же проблема), создав собственное приложение для Windows 8.
Причина, по которой я столкнулся с этой проблемой, состоит в том, что по умолчанию запрос к таблице будет разбиваться на страницы и возвращать только 50 результатов за раз, поэтому я создал шаблон, чтобы получить первые 50, а затем следующие 50, и т.д., пока ответ не вернется с менее чем 50 результатами, а затем разрешит обещание.
Этот код не будет реальным кодом, просто для иллюстрации:
function getAllRows() {
return new WinJS.Promise(function(resolve, reject){
var rows = [];
var recursivelyGetRows = function(skipRows) {
table.skip(skipRows).read()
.then(function(results){
rows = rows.concat(results);
if (results.length < 50) {
resolve(rows);
} else {
recursivelyGetRows(skipRows + 50);
}
})
}
recursivelyGetRows(0);
});
}
поэтому getAllRows() возвращает обещание, которое разрешается только после того, как мы получим результат с менее чем 50 результатами (что указывает на последнюю страницу).
В зависимости от вашего варианта использования вы, вероятно, захотите также бросить обработчик ошибок.
Если неясно, "таблица" - это таблица мобильных служб.
Ответ 3
Вам понадобится использовать шаблон Promise.join(). done(). Передайте массив Promises для join(), который в вашем случае будет набором вызовов xhr. Команда join() вызывается только done(), когда все xhr Promises завершены. Вы получите массив результатов, переданных в done(), который затем вы можете перебрать и начать с нового вызова Promise.join(). Done(). Вещь, которая должна быть в стороне от использования этого подхода, заключается в том, что если один из Promises, переданных в join(), не работает, вся операция рассматривается как условие ошибки.
Извините, у меня нет времени прямо сейчас, чтобы попробовать и заглушить код для вас. Если у меня появится шанс, я постараюсь позже. Но вы должны иметь возможность вставить это в свою рекурсивную функцию и заставить работать.
Ответ 4
Ну, я решил свою проблему; моя рекурсивная функция неверно интерпретировала данные и, таким образом, никогда не переставала рекурсивно. Благодарим вас за помощь, и я обязательно буду смотреть эти скринкасты, поскольку я до сих пор не совсем понимаю структуру цепей Promise.