Объяснение async await в JavaScript

В течение долгого времени разработчикам JavaScript приходилось полагаться, при работе с асинхронным кодом, на функции обратного вызова (callbacks). В результате, многие из нас сталкивались с так называемым "callback hell", когда множество вложенных функций образуют длинную лесницу и код становится совершенно не читаем.

К счастью, затем появились Промисы (Promises). Они предложили гораздо более организованную альтернативу обратным вызовам, и большая часть сообщества быстро перешла к их использованию.

Но теперь в арсенале JavaScript разработчика появился еще и Async/Await, и написание JavaScript-кода скоро станет еще проще!

Что такое Async/Await?

Async / Await - это долгожданная функция JavaScript, которая делает работу с асинхронными функциями более приятной и понятной. Она построена поверх Promises и совместима со всеми существующими API-интерфейсами на основе Promise.

Имя происходит от async и await - два ключевых слова, которые помогут нам очистить наш асинхронный код:

Async - объявляет асинхронную функцию (async function someName(){...}).

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

Await - приостанавливает выполнение асинхронных функций. (var result = await someAsyncCall();).

    Когда это ключевое слово помещено перед вызовом Promise, код ожидает, пока не завершится этот Promise и не вернется результат.
    Await работает только с Promise-ами, и не работает с функциями обратного вызова (колбэками).
    Await может использоваться только внутри async функций.

Вот простой пример, который, надеюсь, прояснит ситуацию:

Предположим, мы хотим получить некоторый JSON-файл с нашего сервера. Мы напишем функцию, которая использует библиотеку axios и отправляет HTTP GET запрос на https://site.com/example.json. Мы должны ждать ответа сервера, поэтому, естественно, этот HTTP-запрос будет асинхронным.

Ниже мы видим две реализации этой задачи. Сначала с использованием Промисов, затем с использованием Async/Await.

// используем Promise
function getJSON(){
    // вручную создаем Промис.
    return new Promise( function(resolve) {
        axios.get('https://site.com/example.json')
            .then( function(json) {
                //Данные из ответа доступны в функции .then
                // Мы возвращаем результат используя resolve.
                resolve(json);
            });
    });
}

//используем Async/Await

// Ключевое слово async автоматически создаст новый Промис и вернет его.
async function getJSONAsync(){

    // Нам не нужно писать блок с .then().
    let json = await axios.get('https://site.com/example.json');

    // Результат GET запроса доступен в переменной json.
    // И мы возвращаем его как в обычной синхронной функции.
    return json;
}

Очевидно, что версия кода с Async/Await намного короче и читабельнее. Помимо используемого синтаксиса, обе функции полностью идентичны - обе они возвращают Промисы и резолвятся после ответа от сервиса. Мы можем вызвать нашу async функцию следующим образом:

getJSONAsync().then( function(result) {
    // Обрабатываем ответ
});

Итак, делает ли Async/Await Промисы устаревшими?

Нет, совсем нет. При работе с Async/Await мы все еще используем Промисы под капотом. Хорошее понимание Промисов на самом деле поможет вам в долгосрочной перспективе и настоятельно рекомендуется.

Есть даже случаи, когда Async/Await не сокращает код, и мы должны обратиться к Промисам за помощью. Один из таких сценариев - это когда нам нужно сделать несколько независимых асинхронных вызовов и дождаться их завершения.

Если мы попытаемся сделать это с помощью async и await, произойдет следующее:

async function getABC() {
  let A = await getValueA(); // ответ через 2 секунды
  let B = await getValueB(); // ответ через 4 секунды
  let C = await getValueC(); // ответ через 3 секунды

  return A*B*C;
}

Каждый await вызов будет ждать, пока предыдущий не вернет результат. Поскольку мы выполняем один вызов за раз, вся функция будет выполняться 9 секунд (2 + 4 + 3).

Это не оптимальное решение, так как три переменные A, B и C не зависят друг от друга. Другими словами, нам не нужно знать значение A до того, как мы получим B. Мы можем получить их в одно и то же время и сократить несколько секунд ожидания.

Чтобы отправить все запросы одновременно, требуется Promise.all(). Это позволит убедиться, что мы имеем все результаты перед продолжением, но асинхронные вызовы будут запускаться параллельно, а не один за другим.

async function getABC() {
  // Promise.all() позволяет послать нам все запросы одновременно.
  let results = await Promise.all([ getValueA, getValueB, getValueC ]);

  return results.reduce((total,value) => total * value);
}

Таким образом, функция займет гораздо меньше времени. Вызовы getValueA и getValueC будут уже завершены к концу getValueB. И время работы функции будет равно времени исполнения самого медленного запроса (getValueB - 4 секунды).

Обработка ошибок в Async/Await

Еще одна отличная вещь в Async/Await заключается в том, что она позволяет нам обнаруживать любые неожиданные ошибки в блоке try/catch. Нам просто нужно обернуть наши Промисы следующим образом:

async function doSomethingAsync(){
    try {
        //Этот асинхронный вызов может завершиться ошибкой.
        let result = await someAsyncCall();
    }
    catch(error) {
        //Если это произошло, мы перехватим ошибку здесь.
    }  
}

Catch будет обрабатывать ошибки, вызванные асинхронными вызовами или любым другим кодом ошибки, который мы могли бы записать внутри блока try.

Если ситуация требует этого, мы также можем поймать ошибки при выполнении функции async. Поскольку все функции async возвращают Promises, мы можем просто включить обработчик события .catch() при их вызове.

// Async функция без блока try/catch.
async function doSomethingAsync(){
    // Этот асинхронный вызов может завершиться ошибкой.
    let result = await someAsyncCall();
    return result;  
}
// Вы перехватываем ошибку в catch().
doSomethingAsync().
    .then(successHandler)
    .catch(errorHandler);
 

Важно выбрать, какой метод обработки ошибок вы предпочитаете, и придерживаться его. Использование обоих вариантов try/catch и .catch () одновременно, скорее всего, приведет к проблемам.

Поддержка браузерами

Async/Await уже доступен в большинстве популярных браузеров. За исключением IE11 - все остальные браузеры выполнят ваш код с async/await без необходимости использования каких-либо дополнительных библиотек.

Поддержка браузерами Async/Await

NodeJS разработчики также могут наслаждаться улучшенным потоком асинхронных операций, если они используют NodeJS версии 8 или новее. В 2017 году эта версия должна стать LTS (рекомендуемая для большинства пользователей).

Если эта совместимость вас не устраивает, есть также несколько JS-транспиляторов, таких как Babel и TypeScript, а также библиотека asyncawait для Node.js, которые предлагают свои собственные кросс-платформенные реализации этой функции.

Заключение

С добавлением Async/Await язык JavaScript делает огромный скачок вперед с точки зрения удобочитаемости кода и простоты использования. Возможность писать асинхронный код, который напоминает регулярные синхронные функции, будет оценен как начинающими, так и опытными JavaScript-разработчиками.

Перевод статьи с tutorialzine.com


Если у Вас возникли вопросы, то для скорейшего получения ответа рекомендуем воспользоваться нашим форумом

Комментарии:

Добавить комментарий


Защитный код
Обновить