Оригинал от:blog.Grossman.IO/как писать…
В ES7 мы можем использовать async и await для написания асинхронных функций.При использовании этого метода наши асинхронные функции выглядят как синхронный код.
В предыдущей версии (ES6) Promise можно было использовать, чтобы упростить процесс асинхронного программирования и избежатьад обратного звонка.
ад обратного звонка
Callback hell — это термин, заимствованный из семантики, и его определение можно объяснить следующей ситуацией:
function AsyncTask() {
asyncFuncA(function(err, resultA){
if(err) return cb(err);
asyncFuncB(function(err, resultB){
if(err) return cb(err);
asyncFuncC(function(err, resultC){
if(err) return cb(err);
// And so it goes....
});
});
});
}
В приведенном выше примере кода постоянные обратные вызовы очень усложняют обслуживание кода и поток управления. Мы могли бы также рассмотреть эту ситуацию, если оператору if необходимо выполнить другие методы, а результатом функции обратного вызова FunctionA является foo.
Оптимизация с помощью промисов
С появлением ES6 и Promises мы можем упростить предыдущий код «ада обратных вызовов» следующим образом:
function asyncTask(cb) {
asyncFuncA.then(AsyncFuncB)
.then(AsyncFuncC)
.then(AsyncFuncD)
.then(data => cb(null, data)
.catch(err => cb(err));
}
Вам кажется, что так удобнее писать?
Однако в реальных бизнес-сценариях обработка асинхронных потоков может оказаться более сложной. Например,
Предположим, на одном из ваших серверов (node.js) вы можете:
- Сохраните данные 1 в базу данных (шаг 1)
- Найдите другие данные 2 на основе сохраненных данных 1 (шаг 2)
- Если данные 2 найдены, выполните некоторые другие асинхронные задачи (другие задачи)
- После выполнения всех задач вам может понадобиться использовать полученные результаты (шаг 1) для обратной связи с пользователем.
- Если во время выполнения задачи возникает ошибка, вы должны сообщить пользователю, на каком шаге произошла ошибка.
После использования синтаксиса Promise он, безусловно, выглядит намного более лаконичным, но все равно кажется мне немного запутанным.
ES7 Async/await
Вам нужен транспилер для использования Async/Await, вы можете использовать плагин babel или Typescript для добавления необходимых инструментов.
На этом этапе, если вы используете async/await, вы обнаружите, что писать код намного удобнее, это позволяет нам писать такой код:
async function asyncTask(cb) {
const user = await UserModel.findById(1);
if(!user) return cb('No user found');
const savedTask = await TaskModel({userId: user.id, name: 'Demo Task'});
if(user.notificationsEnabled) {
await NotificationService.sendNotification(user.id, 'Task Created');
}
if(savedTask.assignedUser.id !== user.id) {
await NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you');
}
cb(null, savedTask);
}
Вышеприведенный код кажется намного читабельнее, но как бороться с ошибками?
Обработка исключений
При выполнении асинхронных задач с использованием Promise могут возникать некоторые ошибки, такие как ошибки подключения к базе данных, ошибки проверки модели базы данных и т. д.
Когда асинхронная функция ожидает возвращаемого значения промиса, когда метод промиса сообщает об ошибке, он выдает исключение, которое можно перехватить в методе перехвата.
При использовании Async/Await мы обычно используем операторы try/catch для перехвата исключений.
try{
//do something
}
catch{
// deal err
}
У меня нет опыта написания строго типизированных языков, поэтому добавление дополнительныхtry/catchоператор, добавляет мне дополнительный код, который, на мой взгляд, очень избыточен и нечист.
Я уверен, что это может быть вопросом личных предпочтений, но вот мой взгляд на это.
Итак, предыдущий код выглядит так:
async function asyncTask(cb) {
try {
const user = await UserModel.findById(1);
if(!user) return cb('No user found');
} catch(e) {
return cb('Unexpected error occurred');
}
try {
const savedTask = await TaskModel({userId: user.id, name: 'Demo Task'});
} catch(e) {
return cb('Error occurred while saving task');
}
if(user.notificationsEnabled) {
try {
await NotificationService.sendNotification(user.id, 'Task Created');
} catch(e) {
return cb('Error while sending notification');
}
}
if(savedTask.assignedUser.id !== user.id) {
try {
await NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you');
} catch(e) {
return cb('Error while sending notification');
}
}
cb(null, savedTask);
}
Найдите другой способ
В последнее время я используюgo-langдля кода и очень нравится их решение, его код выглядит так:
data, err := db.Query("SELECT ...")
if err != nil { return err }
Я думаю, что это лучше, чем использоватьtry/catchБлоки операторов более лаконичны и содержат меньше кода, что делает их более читабельными и удобными в сопровождении.
Но с Await, если вы не предоставите ему try-catch для обработки исключений, программа автоматически завершится при возникновении ошибки (вы видите исключения, которые не выбрасываются). Если вы не предоставите оператор catch для перехвата ошибки, вы не сможете ее контролировать.
Когда я сел с Томером Барнеа (моим хорошим другом) и попытался найти более аккуратное решение, мы получили следующее использование: пожалуйста, помните:Await ожидает возврата значения Promise
асинхронная и ожидающая функция перехватчика исключений
Вооружившись этими знаниями, мы можем создать небольшую общую функцию, которая поможет нам отловить эти ошибки.
// to.js
export default function to(promise) {
return promise.then(data => {
return [null, data];
})
.catch(err => [err]);
}
Эта универсальная функция принимает Promise, затем возвращает возвращаемое значение в виде массива успешной обработки в качестве дополнительного значения и получает первую ошибку, пойманную в методе catch.
import to from './to.js';
async function asyncTask(cb) {
let err, user, savedTask;
[err, user] = await to(UserModel.findById(1));
if(!user) return cb('No user found');
[err, savedTask] = await to(TaskModel({userId: user.id, name: 'Demo Task'}));
if(err) return cb('Error occurred while saving task');
if(user.notificationsEnabled) {
const [err] = await to(NotificationService.sendNotification(user.id, 'Task Created'));
if(err) return cb('Error while sending notification');
}
cb(null, savedTask);
}
Приведенный выше пример — это просто простой вариант использования этого решения, вы можете добавить метод перехвата (например, точку останова отладки) в io.js, который будет получать необработанный объект ошибки, распечатывать журнал или выполнять любую другую операцию, которую вы хотите, а затем возвращать объект после операции.
Мы создали простой пакет NPM для этой библиотеки (Github Repo), вы можете установить его, используя:
npm i await-to-js
Этот пост — просто другой способ поиска функциональности Async/Await, и он полностью основан на личном мнении. Вы можете использовать Promises, просто try-catch и многие другие решения для достижения аналогичных результатов. Пока нравится и работает.
Спровоцировать мышление
Async/await можно использовать в сочетании с promise, то есть catch of promise добавляется в конце управления асинхронным процессом.
async function task(){
return await req();
}
task().catch(e => console.error(e))
Как в сочетании с async/await и Promise.all поймать и обработать исключение?
async function hello(flag){
return new Promise((resolve, reject) => {
if(flag) setTimeout(() => resolve('hello'), 100);
else reject('hello-error');
})
}
async function demo(flag){
return new Promise((resolve, reject) => {
if(flag) setTimeout(() => resolve('demo'), 100);
else reject('demo-error');
})
}
async function main(){
let res = await hello(1).catch(e => console.error(e));
console.log('res => ', res);
let result = await Promise.all([hello(1), demo(1)]);
// let result = await Promise.all([hello(1), demo(0)]).catch(e => console.error('error => ', e));
console.log('result => ', result);
}
main()