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

Node.js Сравнение асинхронной библиотеки - Q vs Async

Я использовал библиотеку kriskowal Q для проекта (веб-скребок/симулятор активности человека) и познакомился с promises, возвращая их и разрешая/отклоняя их, а также основные методы асинхронного управления библиотекой и механизмы броски/захвата ошибок оказались существенными.

Однако я столкнулся с некоторыми проблемами. Мои вызовы promise.then и мои обратные вызовы имеют сверхъестественную тенденцию к формированию пирамид. Иногда это объясняет причины, в других случаях это гарантирует определенный порядок событий. (Я полагаю, что я мог бы исправить некоторые из этих проблем путем рефакторинга, но в будущем я хочу вообще избегать "обратного ада".)

Кроме того, отладка очень расстраивает. Я трачу много времени console.log - свой путь к источнику ошибок и ошибок; после того, как я, наконец, найду их, я начну бросать туда ошибки и поймать их где-то еще с помощью promise.finally, но процесс обнаружения ошибок в первую очередь является трудным.

Кроме того, в моем проекте порядок имеет значение. Мне нужно делать почти все подряд. Зачастую я обнаруживаю, что создаю массивы функций, которые возвращают promises, а затем связывают их друг с другом с помощью Array.prototype.reduce, которые, как я полагаю, мне не нужно делать.

Вот пример одного из моих методов, который использует этот метод сокращения:

removeItem: function (itemId) {

  var removeRegexp = new RegExp('\\/stock\\.php\\?remove=' + itemId);

  return this.getPage('/stock.php')
  .then(function (webpage) {
    var
      pageCount = 5,
      promiseFunctions = [],
      promiseSequence;

    // Create an array of promise-yielding functions that can run sequentially.
    _.times(pageCount, function (i) {
      var promiseFunction = function () {
        var
          promise,
          path;

        if (i === 0) {
          promise = Q(webpage);
        } else {
          path = '/stock.php?p=' + i;
          promise = this.getPage(path);
        }

        return promise.then(function (webpage) {
          var
            removeMatch = webpage.match(removeRegexp),
            removePath;

          if (removeMatch !== null) {
            removePath = removeitemMatch[0];

            return this.getPage(removePath)
            .delay(1000)
            // Stop calling subsequent promises.
            .thenResolve(true);
          }

          // Don't stop calling subsequent promises.
          return false;

        }.bind(this));
      }.bind(this);

      promiseFunctions.push(promiseFunction);
    }, this);

    // Resolve the promises sequentially but stop early if the item is found.
    promiseSequence = promiseFunctions.reduce(function (soFar, promiseFunction, index) {
      return soFar.then(function (stop) {
        if (stop) {
          return true;
        } else {
          return Q.delay(1000).then(promiseFunction);
        }
      });
    }, Q());

    return promiseSequence;
  }.bind(this))
  .fail(function (onRejected) {
    console.log(onRejected);
  });
},

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

Я рассматриваю возможность реорганизации моего проекта с помощью асинхронной библиотеки. Это похоже на Q, но я хочу точно знать, как они отличаются. Впечатление, которое я получаю, заключается в том, что async больше "callback-centric" , а Q "обещает быть ориентированным" .

Вопрос: Учитывая мои проблемы и требования к проекту, что я получу и/или проиграю, используя async через Q? Как сравнивать библиотеки? (В частности, с точки зрения выполнения последовательности задач последовательно и отладки/обработки ошибок?)

4b9b3361

Ответ 1

Обе библиотеки хороши. Я обнаружил, что они служат отдельным целям и могут использоваться в тандеме.

Q предоставляет разработчику обещающие объекты, которые представляют собой будущие представления значений. Полезно для путешествий во времени.

Async предоставляет разработчику асинхронные версии управляющих структур и агрегированных операций.

Пример из одной попытки реализации linter демонстрирует потенциальное единство среди библиотек:

function lint(files, callback) {

    // Function which returns a promise.
    var getMerged = merger('.jslintrc'),

        // Result objects to invoke callback with.
        results = [];

    async.each(files, function (file, callback) {
        fs.exists(file, function (exists) {

            // Future representation of the file contents.
            var contentsPromise,

                // Future representation of JSLINT options from .jslintrc files.
                optionPromise;

            if (!exists) {
                callback();
                return;
            }

            contentsPromise = q.nfcall(fs.readFile, file, 'utf8');
            optionPromise = getMerged(path.dirname(file));

            // Parallelize IO operations.
            q.all([contentsPromise, optionPromise])
                .spread(function (contents, option) {
                    var success = JSLINT(contents, option),
                        errors,
                        fileResults;
                    if (!success) {
                        errors = JSLINT.data().errors;
                        fileResults = errors.reduce(function (soFar, error) {
                            if (error === null) {
                                return soFar;
                            }
                            return soFar.concat({
                                file: file,
                                error: error
                            });
                        }, []);
                        results = results.concat(fileResults);
                    }
                    process.nextTick(callback);
                })
                .catch(function (error) {
                    process.nextTick(function () {
                        callback(error);
                    });
                })
                .done();
        });
    }, function (error) {
        results = results.sort(function (a, b) {
            return a.file.charCodeAt(0) - b.file.charCodeAt(0);
        });
        callback(error, results);
    });
}

Я хочу сделать что-то потенциально-блокирующее для каждого файла. Итак, async.each - очевидный выбор. Я могу распараллеливать связанные операции для каждой итерации с помощью q.all и повторно использовать значения моих опций, если они применяются к 2 или более файлам.

Здесь Async и Q влияют на поток управления программой, а Q представляет собой значения, разрешающие содержимое файла в будущем. Библиотеки хорошо работают вместе. Не нужно "выбирать один над другим".

Ответ 2

Пирамиды обратного вызова в вашем коде могут быть упрощены с использованием композиции обещаний и лексического охвата javascript.

removeItem: function (itemId) {

  var removeRegexp = new RegExp('\\/stock\\.php\\?remove=' + itemId);
  var found = false
  var promise = getPage('/sock.php')

  _.times(5, (i) => {
    promise = promise.then((webpage) => {
      if (found) return true
      var removeMatch = webpage.match(removeRegexp)
      var found = removeMath !== null
      var nextPage = found ? removeMatch[0] : '/stock.php?p='+i+1
      return Q.delay(1000).then(() => this.getPage(nextPage))
    })
  })

  return promise.fail(console.log.bind(console))

},

IMHO async не следует использовать в новом javascript-коде. Promises являются более сложными и позволяют намного более интуитивно понятный код.

Основная причина, по которой node не использовала Promises, была из-за проблем с производительностью, которые в значительной степени были решены такими библиотеками, как Bluebird и Q.

Поскольку синтаксис async/await становится более распространенным, Promises проложит путь для кода, который очень похож на синхронный код.

Ответ 3

Хотя это еще не реальный ответ на мой вопрос (Q vs async), касающийся моей проблемы, я нашел Selenium/WebDriverJs быть жизнеспособным решением.

driver.get('http://www.google.com');
driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');
driver.findElement(webdriver.By.name('btnG')).click();
driver.wait(function() {
  return driver.getTitle().then(function(title) {
    return title === 'webdriver - Google Search';
  });
}, 1000);

WebDriver использует очередь для выполнения promises последовательно, что очень помогает при управлении отступом. Его promises также совместимы с Q.

Создание последовательности promises больше не является проблемой. Простой для цикла будет делать.

Что касается остановки в начале последовательности, не делайте этого. Вместо использования последовательности используйте асинхронный дизайн и ветвь.