Перевод с:Группа переводчиков Lightning Miner
Оригинальный адрес:JavaScript async/await
Оригинальный автор:Charlee Li
Оригинальная ссылка на склад:issue
Переводчик:Xixi20160512
async/await
Представленный в выпуске ES7, он стал огромным улучшением асинхронного программирования в JavaScript. Это позволяет нам обрабатывать асинхронные процессы синхронно, не блокируя основной поток. Однако, чтобы эффективно использовать эту функцию, вам может понадобиться немного мозгов. В этой статье мы рассмотрим async/await с разных точек зрения и покажем, как правильно и эффективно их использовать.
Преимущества асинхронного/ожидания
async/await
Одним из самых больших преимуществ для нас является синхронный стиль программирования. Давайте посмотрим пример:
// async/await
async getBooksByAuthorWithAwait(authorId) {
const books = await bookModel.fetchAll();
return books.filter(b => b.authorId === authorId);
}
// promise
getBooksByAuthorWithPromise(authorId) {
return bookModel.fetchAll()
.then(books => books.filter(b => b.authorId === authorId));
}
Это понятно,async/await
версию легче понять, чем версию промиса. если вы проигнорируетеawait
ключевое слово, этот код выглядит точно так же, как любой другой синхронный язык (скажем, Python).
больше, чем читабельность,async/await
Есть встроенная поддержка браузера. На сегодняшний день все основные браузеры поддерживаютasync
функция.
Все основные браузеры поддерживаютasync
функция. (Источник изображения:руб. newser.com/)
Встроенная поддержка означает, что вам не нужно компилировать код. Что еще более важно, это поможет при отладке. Когда вы нажимаете точку останова при входе в асинхронный метод и переходите кawait
этой строки, вы увидите отладчик вbookModel.fetchAll()
Эта функция ждет некоторое время, когда она выполняется, а затем переходит к следующему.filter
Эта линия! По сравнению с примером обещания, это намного проще, потому что вам нужно.filter
Нажмите еще одну точку останова на этой строке.

Отладка асинхронных функций. Отладчик будет ожидать завершения выполнения в строке ожидания, прежде чем перейти к следующей строке.
Другим менее очевидным преимуществом является то, чтоasync
ключевые слова. он заявляетgetBooksByAuthorWithAwait()
Метод возвращает обещание, поэтому вызывающая сторона может сделать что-то вродеgetBooksByAuthorWithAwait().then(...)
илиawait getBooksByAuthorWithAwait()
Такой безопасный звонок. Посмотрите на этот пример (плохая практика):
getBooksByAuthorWithPromise(authorId) {
if (!authorId) { return null; }
return bookModel.fetchAll()
.then(books => books.filter(b => b.authorId === authorId));
}
В приведенном выше кодеgetBooksByAuthorWithPromise
может вернуть обещание (обычно) илиnull
(особый случай), вызывающая сторона не может безопасно вызывать, когда возвращается null.then()
. использоватьasync
При оформлении декларации такой проблемы не существует.
Async/await может ввести в заблуждение
Некоторые статьи сравнивают async/await с обещаниями, говоря, что это решение следующего поколения в эволюции асинхронного программирования в JavaScript, и я не могу с этим не согласиться. Async/await — это улучшение, но это всего лишь синтаксический сахар, который не изменит полностью наш стиль программирования.
По сути, асинхронные функции все еще обещания. Вы должны понимать промисы, чтобы правильно использовать асинхронные функции.Что еще хуже, в большинстве случаев вам приходится использовать и промисы, и асинхронные функции.
Рассмотрим использование в приведенном выше примереgetBooksByAuthorWithAwait()
а такжеgetBooksByAuthorWithPromises()
. Обратите внимание, что они не только имеют одинаковую функциональность, но и имеют одинаковый интерфейс.
Это означает, что если вы непосредственноgetBooksByAuthorWithAwait()
Если это так, обещание будет возвращено.
Конечно, это не плохо. Толькоawait
Это дает людям ощущение: «Отлично, это может преобразовать асинхронную функцию в синхронную», но это неправильно.
Подводные камни Async/await
затем используяasync/await
Какие ошибки будут допущены в процессе? Вот некоторые из наиболее распространенных примеров.
слишком линейный
несмотря на то чтоawait
Может сделать ваш код похожим на синхронный код, но имейте в виду, что код по-прежнему выполняется асинхронно, и будьте осторожны, чтобы не сделать код слишком линейным.
async getBooksAndAuthor(authorId) {
const books = await bookModel.fetchAll();
const author = await authorModel.fetch(authorId);
return {
author,
books: books.filter(book => book.authorId === authorId),
};
}
Этот код выглядит логически правильным. Однако это неверно.
-
await bookModel.fetchAll()
будет ждатьfetchAll()
Законченный. - потом
await authorModel.fetch(authorId)
будет казнен
Уведомление,authorModel.fetch(authorId)
не зависит отbookModel.fetchAll()
В результате они могут фактически выполняться параллельно. Однако, поскольку использованиеawait
Эти два вызова становятся последовательными, и общее затрачиваемое время будет намного выше, чем при параллельном методе.
Вот правильный способ его использования:
async getBooksAndAuthor(authorId) {
const bookPromise = bookModel.fetchAll();
const authorPromise = authorModel.fetch(authorId);
const book = await bookPromise;
const author = await authorPromise;
return {
author,
books: books.filter(book => book.authorId === authorId),
};
}
Или в более сложных случаях, если вы хотите запросить содержимое списка по очереди, вы должны полагаться на промисы:
async getAuthors(authorIds) {
// WRONG, this will cause sequential calls
// const authors = _.map(
// authorIds,
// id => await authorModel.fetch(id));
// CORRECT
const promises = _.map(authorIds, id => authorModel.fetch(id));
const authors = await Promise.all(promises);
}
Короче говоря, вы должны рассматривать этот рабочий процесс как асинхронный, прежде чем пытаться использоватьawait
Пишите код синхронно. В сложных процессах проще использовать промисы напрямую.
обработка ошибок
С промисами асинхронная функция возвращает два возможных значения: разрешено и отклонено. мы можем использовать.then()
справляться с обычными ситуациями.catch()
Обрабатывать исключения. Однако дляasync/await
Например, обработка исключений может быть немного сложной.
try...catch
Самый стандартный (и я рекомендую) способ сделать это - использоватьtry...catch
выражение. когдаawait
При вызове функции любое отклоненное значение будет выброшено как исключение. Вот пример:
class BookModel {
fetchAll() {
return new Promise((resolve, reject) => {
window.setTimeout(() => {
reject({'error': 400})
}, 1000);
});
}
}
// async/await
async getBooksByAuthorWithAwait(authorId) {
try {
const books = await bookModel.fetchAll();
} catch (error) {
console.log(error); // { "error": 400 }
}
}
Обнаруженная ошибка — это значение reject . После того, как мы перехватим это исключение, у нас будет много способов его обработать:
- Обработайте исключение и верните нормальное значение. (не здесь
catch
заблокировать с помощью любогоreturn
Выражение эквивалентно использованиюreturn undefined
; в то же время возвращаемое значение по-прежнему является разрешенным значением. ) - Сгенерируйте это исключение, если вы хотите, чтобы вызывающая сторона обработала его. Вы можете напрямую выбросить исходный объект ошибки, например.
throw error;
, который позволяет вам связывать промисы с помощьюasync getBooksByAuthorWithAwait()
метод (столбец, например, вы все еще можете делать такие вещи, какgetBooksByAuthorWithAwait().then(...).catch(error => ...)
назовите это так); в качестве альтернативы вы можете использоватьError
Объект оборачивает объект ошибки, например,throw new Error(error)
, используйте этот метод для отображения всех записей стека вызовов в консоли. - Используйте Отклонить, например,
return Promise.reject(error)
, что эквивалентноthrow error
, поэтому этот метод не рекомендуется.
использоватьtry...catch
Преимущества следующие:
- Простой, традиционный. Если у вас есть опыт работы с другими языками, такими как C++ или Java, вам не составит труда понять этот подход.
- вы можете комбинировать несколько
await
Звонок заключен вtry...catch
Блоки для централизованной обработки всех ошибок, если обработка ошибок на каждом этапе не требуется.
Этот подход имеет дефект. из-заtry...catch
Этот блок кода будет захватывать все исключения, другие, которые обычно не перехватываются, обещают, что исключение будет перехвачено в реальном времени. Рассмотрим этот пример:
class BookModel {
fetchAll() {
cb(); // note `cb` is undefined and will result an exception
return fetch('/books');
}
}
try {
bookModel.fetchAll();
} catch(error) {
console.log(error); // This will print "cb is not defined"
}
Выполните этот код, и вы получите сообщение об ошибке в консоли:ReferenceError: cb is not defined
, текст черный. Эта ошибкаconsole.log()
печатается вместо самого JavaScript. В какой-то момент это станет фатальным: еслиBookModel
Будучи глубоко окружен серией вызовов функций, и в то же время один из вызовов обрабатывает ошибку, найти такую ошибку очень сложно.
Заставить функцию возвращать два значения одновременно
Другой подход к обработке ошибок вдохновлен языком Go. Это позволяет асинхронным функциям возвращать как ложные, так и нормальные значения. Более подробное введение можно найти в следующем блоге:
Короче говоря, вы можете использовать асинхронные функции следующим образом:
[err, user] = await to(UserModel.findById(1));
Лично мне такой подход не нравится, потому что он переносит стиль программирования языка Go в JavaScript, что кажется неестественным, но в некоторых случаях может быть полезным.
использовать .поймать
Последний способ справиться с этим, который я собираюсь представить, — это по-прежнему использовать.catch()
.
отзыватьawait
функция: он будет ждать обещания для завершения своей задачи. Также, пожалуйста, напомните,promise.catch()
Также возвращает обещание! Таким образом, мы можем обрабатывать ошибки следующим образом:
// books === undefined if error happens,
// since nothing returned in the catch statement
let books = await bookModel.fetchAll()
.catch((error) => {
console.log(error);
});
При таком подходе есть две второстепенные проблемы:
- Этот подход смешивает промисы и асинхронные функции. Вам все еще нужно понимать, как работают промисы, чтобы читать их.
- Обработка ошибок предшествует обычному потоку, который не очень интуитивно понятен.
В заключение
Представлено в ES7async/await
Ключевые слова, несомненно, являются отличным усовершенствованием асинхронного программирования в JavaScript. Это может упростить чтение и отладку кода. Затем, чтобы использовать их правильно, обещания должны быть полностью поняты, потому что они просто синтаксический сахар, а лежащая в основе технология по-прежнему является обещаниями.
Надеюсь, эта статья даст вам некоторую информацию оasync/await
, помогая вам избежать некоторых распространенных ошибок. Спасибо за чтение, и, пожалуйста, дайте мне палец вверх, если вам это нравится.