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

Являются ли вложенные promises нормальными значениями в node.js?

Проблема, с которой я столкнулся в течение двух недель при изучении node.js, заключается в том, как выполнять синхронное программирование с использованием node. Я обнаружил, что независимо от того, как я стараюсь делать что-то последовательно, я всегда получаю вложенные обещания. Я обнаружил, что есть такие модули, как Q, чтобы помочь с цепочкой обещаний в части обслуживания.

Что я не понимаю во время исследования, так это Promise.all(), Promise.resolve() и Promise.reject(). Promise.reject довольно Promise.reject по названию, но при написании приложения я не понимаю, как включить любое из них в функции или объекты, не нарушая поведение приложения.

Существует определенная кривая обучения для node.js, когда он исходит из языка программирования, такого как Java или С#. Вопрос, который все еще остается, состоит в том, является ли цепочка обещаний нормальной (лучшая практика) в node.js.

Пример:

driver.get('https://website.com/login').then(function () {
    loginPage.login('company.admin', 'password').then(function () {
        var employeePage = new EmployeePage(driver.getDriver());

        employeePage.clickAddEmployee().then(function() {
            setTimeout(function() {
                var addEmployeeForm = new AddEmployeeForm(driver.getDriver());

                addEmployeeForm.insertUserName(employee.username).then(function() {
                    addEmployeeForm.insertFirstName(employee.firstName).then(function() {
                        addEmployeeForm.insertLastName(employee.lastName).then(function() {
                            addEmployeeForm.clickCreateEmployee().then(function() {
                                employeePage.searchEmployee(employee);
                            });
                        });
                    });
                });
            }, 750);
        });
    });
});
4b9b3361

Ответ 1

Нет, одно из больших преимуществ Promises заключается в том, что вы можете сохранять свой асинхронный код линейным, а не вложенным (ад обратного вызова от стиля передачи продолжения).

Обещания дают вам операторы возврата и выдачи ошибок, которые вы теряете при продолжении передачи.

Вам нужно вернуть обещание из ваших асинхронных функций, чтобы вы могли связать возвращаемое значение.

Вот пример:

driver.get('https://website.com/login')
  .then(function() {
    return loginPage.login('company.admin', 'password')
  })
  .then(function() {
    var employeePage = new EmployeePage(driver.getDriver());
    return employeePage.clickAddEmployee();
  })
  .then(function() {
    setTimeout(function() {
      var addEmployeeForm = new AddEmployeeForm(driver.getDriver());

      addEmployeeForm.insertUserName(employee.username)
        .then(function() {
          return addEmployeeForm.insertFirstName(employee.firstName)
        })
        .then(function() {
          return addEmployeeForm.insertLastName(employee.lastName)
        })
        .then(function() {
          return addEmployeeForm.clickCreateEmployee()
        })
        .then(function() {
          return employeePage.searchEmployee(employee)
        });
    }, 750);
});

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

Пример:

addEmployeeForm.insertUserName(employee.username)
    .then(function() {
        // these two functions will be invoked immediately and resolve concurrently
        return Promise.all([
            addEmployeeForm.insertFirstName(employee.firstName),
            addEmployeeForm.insertLastName(employee.lastName)
        ])
    })
    // this will be invoked after both insertFirstName and insertLastName have succeeded
    .then(function() {
        return addEmployeeForm.clickCreateEmployee()
    })
    .then(function() {
        return employeePage.searchEmployee(employee)
    })
    // if an error arises anywhere in the chain this function will be invoked
    .catch(function(err){
        console.log(err)
    });

Promise.resolve() и Promise.reject() - это методы, используемые при создании Promise. Они используются, чтобы обернуть асинхронную функцию с помощью обратных вызовов, чтобы вы могли работать с Promises вместо обратных вызовов.

Решимость разрешит/выполнить обещание (это означает, что приковано then метод будет вызываться с результирующим значением).
Отклонить будет отвергать обещание (это означает, что любой прикован then метод не будет называться, но первый прикован catch метод будет вызываться с ошибкой, возникшей).

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

Ответ 2

Используйте библиотеку async и используйте async.series вместо вложенных цепочек, которые выглядят очень уродливо и трудно отлаживать/понимать.

async.series([
    methodOne,
    methodTwo
], function (err, results) {
    // Here, results is the value from each function
    console.log(results);
});

Метод Promise.all(iterable) возвращает обещание, которое разрешает, когда все promises в итерабельном аргументе разрешились или отклоняются по причине первого отправленного обещания, которое отклоняет.

var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, "foo");
}); 

Promise.all([p1, p2, p3]).then(function(values) { 
  console.log(values); // [3, 1337, "foo"] 
});

Метод Promise.resolve(value) возвращает объект Promise, который разрешен с заданным значением. Если значение является допустимым (т.е. Имеет метод then), возвращенное обещание будет "следовать", что затем, принимая его возможное состояние; в противном случае возвращаемое обещание будет выполнено со значением.

var p = Promise.resolve([1,2,3]);
p.then(function(v) {
  console.log(v[0]); // 1
});

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Ответ 3

Я удалил ненужное вложение. Я использую синтаксис из "bluebird" (моя предпочтительная библиотека Promise) http://bluebirdjs.com/docs/api-reference.html

var employeePage;

driver.get('https://website.com/login').then(function() {
    return loginPage.login('company.admin', 'password');
}).then(function() {
    employeePage = new EmployeePage(driver.getDriver());    
    return employeePage.clickAddEmployee();
}).then(function () {
    var deferred = Promise.pending();
    setTimeout(deferred.resolve,750);
    return deferred.promise;
}).then(function() {
    var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
    return Promise.all([addEmployeeForm.insertUserName(employee.username),
                        addEmployeeForm.insertFirstName(employee.firstName),
                        addEmployeeForm.insertLastName(employee.lastName)]);
}).then(function() {
    return addEmployeeForm.clickCreateEmployee();
}).then(function() {
    return employeePage.searchEmployee(employee);
}).catch(console.log);

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

  • Нет необходимости использовать библиотеку async при работе с promises. Promises являются очень мощными сами по себе, и я думаю, что это анти-шаблон для смешивания Promises и библиотек, таких как async.

  • Обычно вам следует избегать использования стиля var debferred = Promise.pending()... если

'при упаковке API обратного вызова, который не соответствует стандарту условность. Как setTimeout: '

https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns

Для примера setTimeout.. создайте "отложенное" обещание... разрешите обещание внутри setTimeout, а затем верните обещание вне setTimeout. Это может показаться немного неинтуитивным. Посмотрите на этот пример, я ответил на другой вопрос. Предложение Q.js с node. Отсутствует обработчик ошибок в `socket`. TypeError: Невозможно вызвать метод 'then' из undefined

Как правило, вы можете избежать использования Promise.promisify(someFunction), чтобы преобразовать функцию типа обратного вызова в функцию возврата Promise.

  1. Promise.all Допустим, вы делаете несколько вызовов для службы, которые возвращаются асинхронно. Если они не зависят друг от друга, вы можете совершать вызовы одновременно.

Просто передайте вызовы функций в виде массива. Promise.all([promReturningCall1, promReturningCall2, promReturningCall3]);

  1. Наконец, добавьте блокирующий блок до самого конца, чтобы убедиться, что вы поймаете какую-либо ошибку. Это приведет к любым исключениям в любой точке сети.

Ответ 4

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

Возьмите этот бит кода

Promise.prototype.bind = Promise.prototype.then;

const coro = g => {
  const next = x => {
    let {done, value} = g.next(x);
    return done ? value : value.bind(next);
  }
  return next();
};

Используя его, вы можете превратить свою глубоко вложенную цепочку Promise в это

coro(function* () {
  yield driver.get('https://website.com/login')
  yield loginPage.login('company.admin', 'password');
  var employeePage = new EmployeePage(driver.getDriver());
  yield employeePage.clickAddEmployee();
  setTimeout(() => {
    coro(function* () {
      var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
      yield addEmployeeForm.insertUserName(employee.username);
      yield addEmployeeForm.insertFirstName(employee.firstName);
      yield addEmployeeForm.insertLastName(employee.lastName);
      yield addEmployeeForm.clickCreateEmployee();
      yield employeePage.searchEmployee(employee);
    }());
  }, 750);
}());

Используя именованные генераторы, мы можем сделать это еще более разборчивым

// don't forget to assign your free variables
// var driver = ...
// var loginPage = ...
// var employeePage = new EmployeePage(driver.getDriver());
// var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
// var employee = ...

function* createEmployee () {
  yield addEmployeeForm.insertUserName(employee.username);
  yield addEmployeeForm.insertFirstName(employee.firstName);
  yield addEmployeeForm.insertLastName(employee.lastName);
  yield addEmployeeForm.clickCreateEmployee();
  yield employeePage.searchEmployee(employee);
}

function* login () {
  yield driver.get('https://website.com/login')
  yield loginPage.login('company.admin', 'password');
  yield employeePage.clickAddEmployee();
  setTimeout(() => coro(createEmployee()), 750);
}

coro(login());

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

Если вы намерены использовать сопрограммы для этой цели, я рекомендую вам проверить co library.

Надеюсь, что это поможет.

PS не уверен, почему вы используете setTimeout таким образом. Какой смысл ждать 750 мс?

Ответ 5

Следующий шаг - перейти от вложения к цепочке. Вы должны понимать, что каждое обещание - это изолированное обещание, которое может быть прикован к родительскому обещанию. Другими словами, вы можете сгладить цепочку promises. Результат каждого обещания может быть передан на следующий.

Вот отличная статья в блоге: Сжатие обещаний Цепочки. Он использует Angular, но вы можете игнорировать это и посмотреть, как глубокая вложенность promises превращается в цепочку.

Еще один хороший ответ прямо здесь: StackOverflow: Понимание javascript promises; стеки и цепочка.

Ответ 6

Вы можете связать promises следующим образом:

driver.get('https://website.com/login').then(function () {
    return loginPage.login('company.admin', 'password')
)}.then(function () {
    var employeePage = new EmployeePage(driver.getDriver());

    return employeePage.clickAddEmployee().then(function() {
        setTimeout(function() {
            var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
        return addEmployeeForm.insertUserName(employee.username).then(function() {
                retun addEmployeeForm.insertFirstName(employee.firstName)
         }).then(function() {
                return addEmployeeForm.insertLastName(employee.lastName)
         }).then(function() {
             return addEmployeeForm.clickCreateEmployee()
         }).then(function () {
             retrun employeePage.searchEmployee(employee);
        })}, 750);
});

}); });