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

Как использовать q.js promises для работы с несколькими асинхронными операциями

Примечание. Этот вопрос также перекрестно размещен в списке рассылки Q.js по здесь.


У меня была ситуация с несколькими асинхронными операциями, а ответ, который я принял, указал, что использование Promises с использованием библиотеки, такой как q.js, будет более выгодным.

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

Репо здесь, а самым важным файлом является this.

Требование состоит в том, что я хочу, чтобы pageSizes был непустым после прохождения всех выгруженных файлов.

Проблема в том, что операции FileAPI внутри функции getSizeSettingsFromPage приводят к асинхронному getSizeSettingsFromPage.

Поэтому я не могу разместить checkWhenReady(); как это.

function traverseFiles() {
  for (var i=0, l=pages.length; i<l; i++) {
    getSizeSettingsFromPage(pages[i], calculateRatio);   
  }
  checkWhenReady(); // this always returns 0.
}

Это работает, но это не идеально. Я предпочитаю называть checkWhenReady только один раз после того, как все pages претерпели эту функцию calculateRatio успешно.

function calculateRatio(width, height, filename) {
  // .... code 
  pageSizes.add(filename, object);
  checkWhenReady(); // this works but it is not ideal. I prefer to call this method AFTER all the `pages` have undergone calculateRatio
  // ..... more code...
}

Как мне реорганизовать код, чтобы использовать Promises в Q.js?

4b9b3361

Ответ 1

Мои предложения по работе с Q.js приведены ниже. Ключ в том, что в любое время, когда вы хотите сделать что-то асинхронно, вы должны вернуть обещание, и как только задача будет завершена, вы должны решить это обещание. Это позволяет вызывающим функциям прослушивать выполнение задачи, а затем делать что-то еще.

Как и прежде, я прокомментировал свои изменения с помощью // ***. Дайте мне знать, если у вас возникнут дополнительные вопросы.

        function traverseFiles() {
            // *** Create an array to hold our promises
            var promises = [ ];
            for (var i=0, l=pages.length; i<l; i++) {
                // *** Store the promise returned by getSizeSettingsFromPage in a variable
                promise = getSizeSettingsFromPage(pages[i]);
                promise.then(function(values) {
                    var width = values[0],
                        height = values[1],
                        filename = values[2];
                    // *** When the promise is resolved, call calculateRatio
                    calculateRatio(width, height, filename);
                });
                // *** Add the promise returned by getSizeSettingsFromPage to the array
                promises.push(promise);
            }
            // *** Call checkWhenReady after all promises have been resolved
            Q.all(promises).then(checkWhenReady);
        }

        function getSizeSettingsFromPage(file) {
            // *** Create a Deferred
            var deferred = Q.defer();
            reader = new FileReader();
            reader.onload = function(evt) {
                var image = new Image();
                image.onload = function(evt) {
                    var width = this.width;
                    var height = this.height;
                    var filename = file.name;
                    // *** Resolve the Deferred
                    deferred.resolve([ width, height, filename ]);
                };
                image.src = evt.target.result;
            };
            reader.readAsDataURL(file);
            // *** Return a Promise
            return deferred.promise;
        }

Изменить

defer создает Отложенный, который содержит две части: a promise и resolve. promise возвращается getSizeSettingsFromPage. В принципе, возвращение обещания - это способ для функции сказать: "Я вернусь к вам позже". Как только функция выполнила задание (в этом случае, как только событие image.onload было запущено), функция resolve используется для разрешения обещания. Это указывает на то, что ждет обещание о завершении задачи.

Вот более простой пример:

function addAsync(a, b) {
    var deferred = Q.defer();
    // Wait 2 seconds and then add a + b
    setTimeout(function() {
        deferred.resolve(a + b);
    }, 2000);
    return deferred.promise;
}

addAsync(3, 4).then(function(result) {
    console.log(result);
});
// logs 7 after 2 seconds

Функция addAsync добавляет два числа, но она ждет 2 секунды перед их добавлением. Поскольку он асинхронен, он возвращает обещание (deferred.promse) и разрешает обещание после ожидания 2 секунды (deferred.resolve). Метод then можно вызвать по обещанию и передать функцию обратного вызова, которая будет выполнена после того, как обещание будет разрешено. Функция обратного вызова передается в значении разрешения обещания.

В вашем случае у нас был массив promises, и нам нужно было дождаться, пока все они будут выполнены до выполнения функции, поэтому мы использовали Q.all. Вот пример:

function addAsync(a, b) {
    var deferred = Q.defer();
    // Wait 2 seconds and then add a + b
    setTimeout(function() {
        deferred.resolve(a + b);
    }, 2000);
    return deferred.promise;
}

Q.all([
    addAsync(1, 1),
    addAsync(2, 2),
    addAsync(3, 3)
]).spread(function(result1, result2, result3) {
    console.log(result1, result2, result3);
});
// logs "2 4 6" after approximately 2 seconds

Ответ 2

Похоже, вы должны использовать функцию Q.all для создания главного обещания, соответствующего тому, когда все getSizeSettings promises заполнены.

https://github.com/kriskowal/q#combination

var ps = [];
for (var i=0, l=pages.length; i<l; i++) {
   ps[i] = getSizeSettingsFromPage(pages[i], calculateRatio);   
}

Q.all(ps).then(function(){ callWhenReady() })

Большинство библиотек обещаний должны предоставить аналогичный метод для такого рода синхронизации. Если вы когда-либо сталкиваетесь с тем, что не делает, что вы можете сделать, это подкрепить каждое индивидуальное обещание обратным вызовом, которое увеличивает общий счетчик при его вызове. Когда ваш счетчик достигнет n, вы знаете, что вы уже разрешили все promises, чтобы вы могли вызвать обратный вызов incrementor и "реальный" обратный вызов.

//If you did not have Q.all available
//Or had to code this without a promise library

var to_go = pages.length;
for (var i=0, l=pages.length; i<l; i++) {
   getSizeSettingsFromPage(pages[i], calculateRatio)
   .then(function(){
       to_go--;
       if(to_go == 0){
           callWhenReady()
       }
   });
}

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

var go = function(i){
    if(i>=pages.length){
        return call_next_step()
    }else{
        return do_ith_calculation(i)
        .then(function(){
            return go(i+1)
        })
    }
};
go(0);