Являются ли вложенные 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);
});
});
});
Ответы
Ответ 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.
- Promise.all
Допустим, вы делаете несколько вызовов для службы, которые возвращаются асинхронно.
Если они не зависят друг от друга, вы можете совершать вызовы одновременно.
Просто передайте вызовы функций в виде массива.
Promise.all([promReturningCall1, promReturningCall2, promReturningCall3]);
- Наконец, добавьте блокирующий блок до самого конца, чтобы убедиться, что вы поймаете какую-либо ошибку. Это приведет к любым исключениям в любой точке сети.
Ответ 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);
});
});
});