У меня в последнее время много дел, давно не учился, да и сам влепилмаленький сайт, добро пожаловать звезда.
JavaScript async/await: The Good Part, Pitfalls and How to Use
Представлено ES7async/awaitФункции — это серьезное улучшение асинхронного программирования в JS. Это дает нам возможность получать ресурсы асинхронно, используя стиль синхронного кода, не блокируя основной поток. Конечно, его использование также требует некоторых навыков, и в этой статье мы рассмотрим его с разных сторон.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соотношение версийpromiseВерсия проще и понятнее. если вы проигнорируетеawaitключевое слово, то код похож на другие синхронные языки программирования, такие какPython.
Преимущество не только в удобочитаемости,async/awaitУже изначально поддерживается браузерами. Сегодня все основные браузеры имеютполностью поддерживаю.
Встроенная поддержка означает, что вам не нужноконвертироватькода и, что более важно, облегчает отладку. когда вы находитесь в функцииawaitКогда вы нажмете точку останова на строке кода и перейдете к следующей строке, вы обнаружите, что отладчикbookModel.fetchAll()Во время операции он ненадолго остановился, а потом действительно вошел в.filterстрока кода! это лучше, чемpromiseОтладка проще, потому что вам нужно.fliterНажмите еще одну точку останова в строке кода.
Еще одно малозаметное преимущество заключается в том, чтоasyncключевые слова. это показываетgetBooksByAuthorWithAwait()Возвращаемое значение функции должно бытьpromise, поэтому вызывающая сторона может использоватьgetBooksByAuthorWithAwait().then(...)или безопасно использоватьawait getBooksByAuthorWithAwait(). См. код (плохая практика!):
getBooksByAuthorWithPromise(authorId) {
if (!authorId) {
return null;
}
return bookModel.fetchAll()
.then(books => books.filter(b => b.authorId === authorId));
}
}
Во фрагменте кода вышеgetBooksByAuthorWithPromiseможет вернутьpromise(нормальный) илиnullзначение (исключение), и в этом случае вызывающая сторона не может безопасно использовать.then(). и имеютasyncзаявление, чтобы избежать этой неопределенности.
async/awaitиногда вводящий в заблуждение
Некоторые статьи будут сравниватьasync/awaitа такжеpromiseи утверждают, что это следующее поколениеJSАсинхронное программирование, с которым я не согласен.async/await КонечноУлучшение, но это всего лишь синтаксический сахар, и он не изменит полностью наш стиль программирования.
По сути,asyncфункция по-прежнемуpromises. при правильном использованииasyncПрежде чем вам нужно понятьpromise, вероятно, вы используетеasyncтакже нужно использоватьpromise.
Просмотрите код вышеgetBooksByAuthorWithAwait()а такжеgetBooksByAuthorWithPromises()функции, они не только функционируют точно так же, но и имеют такой же интерфейс.
Это означает, что вызов напрямуюgetBooksByAuthorWithAwait()вернетpromise.
Это не обязательно плохо, и большинство людей думают, что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),
};
}
И если вы хотите получить все элементы списка по очереди, вам придется полагаться наpromises:
async getAuthors(authorIds) {
// 错误,这会导致`串行执行`
// const authors = _.map(
// authorIds,
// id => await authorModel.fetch(id));
// 正确
const promises = _.map(authorIds, id => authorModel.fetch(id));
const authors = await Promise.all(promises);
}
Короче говоря, вам все равно нужно относиться к рабочему процессу как к асинхронному и пытаться использоватьawaitдля написания синхронного кода. В более сложных рабочих процессах используйте напрямуюpromiseМожет быть удобнее.
обработка ошибок
комбинироватьpromises, асинхронная функция имеет только два возможных возвращаемых значения:resolve值а такжеreject值, то мы можем использовать.then()справляться с обычными ситуациями,.catch()Обрабатывать исключения. ноasync/awaitОбработка ошибок требует некоторого навыка.
try...catch
Самый распространенный (и я рекомендую) способ - использоватьtry..catch. когдаawaitВо время операции любоеreject值будет выброшено как исключение. См. код:
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()функция для использованияpromiseцепные операции (т.е.:getBooksByAuthorWithAwait().then(...).catch(error => ...)); или используйтеErrorobject обертывает ваш объект ошибки, напримерthrow new Error(error), поэтому вы можете увидеть полный журнал стека при просмотре ошибки в консоли. -
rejectобъект ошибки, напримерreturn Promise.reject(error). Это эквивалентно первому подходу, поэтому не рекомендуется.
использоватьtry...catchПреимущества заключаются в следующем:
- Простой, традиционный, если у вас есть такие вещи, как
JavaилиC++Опыт программирования, легко понять. - в
try...catchВ блоке кода вы можетеtryБлоки кода переносятся на несколько строкawaitзаявление, и если предварительная обработка ошибок не требуется, вы можете сделать это в одном месте (т.е.catchблок кода) для обработки ошибок.
Эта схема все же имеет свои недостатки,try...catchМожет отловить все ошибки в блоке кода, в том числе те, которые неpromisesОшибки пойманы. См. код:
class BookModel {
fetchAll() {
cb(); // `cb` 因为没有被定义所有会导致异常
return fetch('/books');
}
}
try {
bookModel.fetchAll();
} catch(error) {
console.log(error); // 这里打印 "cb is not defined"
}
Запустите этот код, и вы получите в консолиReferenceError: cb is not definedВывод информации черным шрифтом. Вы должны знать, что ошибка здесь черезconsole.log()вывод, неJSсама бросает(JSОшибки выделены красным шрифтом). Иногда это может быть фатальным: еслиBookModelБудучи глубоко вложенными и обернутыми вызовами некоторых других функций, один из которых проглатывает исключение, становится крайне сложно найти в примере такую ошибку.
заставить функцию возвращать все значения
кGoЯзыковая эвристика, еще один способ обработки ошибок — разрешитьasyncвозврат функции异常а также结果два значения (см.How to write async await without try-catch blocks in Javascript), то есть можно использоватьasyncфункция:
[err, user] = await to(UserModel.findById(1));
Я лично не рекомендую использовать эту реализацию, поскольку она ставитGoязыковой стиль принесJS, что кажется мне неестественным, но в отдельных случаях крайне уместно использовать.
использовать.catch()
Последний метод заключается в продолжении использования.catch().
передуматьawaitчто он делает: он ждетpromiseвыполнить работу, также помнитеpromise.catch()также вернетpromise! Таким образом, мы можем обрабатывать ошибки с помощью этого:
// 如果发生异常,但是 catch 语句没有显示返回,那么 books === undefined
let books = await bookModel.fetchAll()
.catch((error) => { console.log(error); });
У этой реализации есть два недостатка:
- это
promiseа такжеasyncфункция перемешивания. тебе нужно понятьpromiseпонять это. - Обработка ошибок перед возвратом, что не очень интуитивно понятно.
В заключение
ES7изasync/awaitпара функцийJSАсинхронное программирование — это огромное улучшение. Это делает код более читабельным и упрощает отладку. Но чтобы правильно ими пользоваться, нужно досконально пониматьpromise. Поскольку это всего лишь синтаксический сахар, технология, на которую он опирается, по-прежнему актуальна.promise.