Поиск по сайту:

Изучение функций Async/Await в JavaScript


Введение

Промисы дают нам более простой способ последовательной обработки асинхронности в нашем коде. Учитывая, что наш мозг не предназначен для эффективной борьбы с асинхронностью, это очень долгожданное дополнение. Функции Async/await, новое дополнение ES2017 (ES8), еще больше помогают нам, позволяя нам писать полностью синхронный код, выполняя асинхронные задачи за кулисами.

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

Простой пример

В следующем примере мы сначала объявляем функцию, которая возвращает обещание, которое разрешается в значение 🤡 через 2 секунды. Затем мы объявляем функцию async и ожидаем выполнения промиса, прежде чем выводить сообщение на консоль:

function scaryClown() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('🤡');
    }, 2000);
  });
}

async function msg() {
  const msg = await scaryClown();
  console.log('Message:', msg);
}

msg(); // Message: 🤡 <-- after 2 seconds

await — это новый оператор, используемый для ожидания разрешения или отклонения промиса. Его можно использовать только внутри асинхронной функции.

Сила асинхронных функций становится более очевидной, когда выполняется несколько шагов:

function who() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('🤡');
    }, 200);
  });
}

function what() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('lurks');
    }, 300);
  });
}

function where() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('in the shadows');
    }, 500);
  });
}

async function msg() {
  const a = await who();
  const b = await what();
  const c = await where();

  console.log(`${ a } ${ b } ${ c }`);
}

msg(); // 🤡 lurks in the shadows <-- after 1 second

Тем не менее, предостережение: в приведенном выше примере каждый шаг выполняется последовательно, при этом каждый дополнительный шаг ожидает разрешения или отклонения предыдущего шага, прежде чем продолжить. Если вместо этого вы хотите, чтобы шаги выполнялись параллельно, вы можете просто использовать Promise.all, чтобы дождаться выполнения всех обещаний:

// ...

async function msg() {
  const [a, b, c] = await Promise.all([who(), what(), where()]);

  console.log(`${ a } ${ b } ${ c }`);
}

msg(); // 🤡 lurks in the shadows <-- after 500ms

Promise.all возвращает массив с разрешенными значениями после разрешения всех переданных промисов.

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

Обещание-возвращение

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

async function hello() {
  return 'Hello Alligator!';
}

const b = hello();

console.log(b); // [object Promise] { ... }

Поскольку то, что возвращается, является обещанием, вместо этого вы можете сделать что-то вроде этого:

async function hello() {
  return 'Hello Alligator!';
}

const b = hello();

b.then(x => console.log(x)); // Hello Alligator!

…или просто это:

async function hello() {
  return 'Hello Alligator!';
}

hello().then(x => console.log(x)); // Hello Alligator!

Различные формы

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

Выражение асинхронной функции

Вот асинхронная функция из нашего первого примера, но определенная как функциональное выражение:

const msg = async function() {
  const msg = await scaryClown();
  console.log('Message:', msg);
}

Функция асинхронной стрелки

Вот тот же пример еще раз, но на этот раз определенный как функция стрелки:

const msg = async () => {
  const msg = await scaryClown();
  console.log('Message:', msg);
}

Обработка ошибок

Что еще очень хорошо в асинхронных функциях, так это то, что обработка ошибок также выполняется полностью синхронно, с использованием старых добрых операторов try…catch. Давайте продемонстрируем, используя обещание, которое будет отклонено в половине случаев:

function yayOrNay() {
  return new Promise((resolve, reject) => {
    const val = Math.round(Math.random() * 1); // 0 or 1, at random

    val ? resolve('Lucky!!') : reject('Nope 😠');
  });
}

async function msg() {
  try {
    const msg = await yayOrNay();
    console.log(msg);
  } catch(err) {
    console.log(err);
  }
}

msg(); // Lucky!!
msg(); // Lucky!!
msg(); // Lucky!!
msg(); // Nope 😠
msg(); // Lucky!!
msg(); // Nope 😠
msg(); // Nope 😠
msg(); // Nope 😠
msg(); // Nope 😠
msg(); // Lucky!!

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

async function msg() {
  const msg = await yayOrNay();
  console.log(msg);
}

msg().catch(x => console.log(x));

Эта синхронная обработка ошибок работает не только при отклонении промиса, но и при возникновении фактической ошибки времени выполнения или синтаксической ошибки. В следующем примере во второй раз при вызове нашей функции msg мы передаем значение number, в котором нет метода toUpperCase. цепочка прототипов. Наш блок try…catch точно так же перехватывает эту ошибку:

function caserUpper(val) {
  return new Promise((resolve, reject) => {
    resolve(val.toUpperCase());
  });
}

async function msg(x) {
  try {
    const msg = await caserUpper(x);
    console.log(msg);
  } catch(err) {
    console.log('Ohh no:', err.message);
  }
}

msg('Hello'); // HELLO
msg(34); // Ohh no: val.toUpperCase is not a function

Асинхронные функции с APIS на основе обещаний

Как мы показали в нашем учебнике по Fetch API, веб-API, основанные на промисах, являются идеальными кандидатами для асинхронных функций:

async function fetchUsers(endpoint) {
  const res = await fetch(endpoint);
  let data = await res.json();

  data = data.map(user => user.username);

  console.log(data);
}

fetchUsers('https://jsonplaceholder.typicode.com/users');
// ["Bret", "Antonette", "Samantha", "Karianne", "Kamren", "Leopoldo_Corkery", "Elwyn.Skiles", "Maxime_Nienow", "Delphine", "Moriah.Stanton"]

Поддержка браузера:

Заключение

До появления функций Async/await код JavaScript, основанный на большом количестве асинхронных событий, (например, код, выполняющий множество вызовов API), заканчивался тем, что некоторые называют «адом обратных вызовов» — цепочкой. функций и обратных вызовов, которые было очень трудно читать и понимать.

Асинхронность и ожидание позволяют нам писать асинхронный код JavaScript, который читается намного четче.