【Перевод】async/await должен знать должен знать

внешний интерфейс JavaScript

Перевод с:Группа переводчиков 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функция.

68747470733a2f2f63646e2d696d616765732d312e6d656469756d2e636f6d2f6d61782f3830302f312a633662596168414255447047674d704a56616b3441672e706e67

Все основные браузеры поддерживаютasyncфункция. (Источник изображения:руб. newser.com/)

Встроенная поддержка означает, что вам не нужно компилировать код. Что еще более важно, это поможет при отладке. Когда вы нажимаете точку останова при входе в асинхронный метод и переходите кawaitэтой строки, вы увидите отладчик вbookModel.fetchAll()Эта функция ждет некоторое время, когда она выполняется, а затем переходит к следующему.filterЭта линия! По сравнению с примером обещания, это намного проще, потому что вам нужно.filterНажмите еще одну точку останова на этой строке.

2

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

Другим менее очевидным преимуществом является то, что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),  
    };
}

Этот код выглядит логически правильным. Однако это неверно.

  1. await bookModel.fetchAll()будет ждатьfetchAll()Законченный.
  2. потом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. Это позволяет асинхронным функциям возвращать как ложные, так и нормальные значения. Более подробное введение можно найти в следующем блоге:

How to write async await without try-catch blocks in Javascript*ES7 Async/await позволяет нам, как разработчикам, писать асинхронный код JS, который выглядит синхронно.В текущей версии JS мы…*blog.grossman.io

Короче говоря, вы можете использовать асинхронные функции следующим образом:

[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, помогая вам избежать некоторых распространенных ошибок. Спасибо за чтение, и, пожалуйста, дайте мне палец вверх, если вам это нравится.