Получение данных в Интернете - это асинхронная задача, которой многие опасаются из-за ее асинхронности. Обычно у нас есть тенденция писать обратные вызовы для обработки асинхронных функций, потому что их легче писать и понимать. Это может сработать, когда у вас есть несколько асинхронных функций для выполнения, но по мере того, как база кода начинает расти и / или если возникает необходимость в большом количестве асинхронных функций для выполнения, у вас будут асинхронные функции, переданные как обратные вызовы асинхронным функциям рекурсивно . Давайте рассмотрим этот пример

Здесь у вас есть массив супергероев, и для каждого из них вы должны получить данные, и после завершения выборки необходимо вызвать функцию completedFetchingData. При асинхронном характере запросов ajax, как вы можете определить момент времени, когда все данные были извлечены? Давайте посмотрим на эту суть, где мы сделали простой ванильный запрос AJAX на httpbin для всех супергероев.

Будет ли приведенный выше код работать так, как задумано?

К сожалению, для наших нужд это не всегда так. Здесь мы добавили условие для выполнения функции completedFetchingData после получения данных о последнем супергерое. Но не обязательно, что получение данных о последнем супергерое завершает выполнение в конце.

Из-за асинхронного характера запросов ajax и всех запросов ajax, выполняемых параллельно, данные супергероев могут быть извлечены в любом случайном порядке. Таким образом, если данные о последнем супергерое были получены раньше других супергероев, completedFetchingData выполняется даже до того, как данные для всех были получены.

Мы могли бы справиться с вышеуказанной проблемой, получая данные для одного супергероя после получения данных для другого в обратном вызове рекурсивно для всех супергероев. Это требует, чтобы мы добавили функцию completedFetchingData к обратному вызову для запроса ajax для последнего супергероя. Это сработает, но теперь вы заставили свои вызовы ajax работать более синхронно, а ваш код выглядит уродливым и трудным для тестирования. Кроме того, вы, вероятно, попадете в ад обратных вызовов.

Представьте себе, что в списке есть сотни или тысячи супергероев. Масштабируемость сейчас… Наверное, нет! Здесь и вступает в игру потребность в обещаниях.

Обещания

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

let promise = new Promise((resolve, reject) => {
 if(/* asynchronous code execution is successful */) {
  resolve(/* result */);
 } else {
  reject(/* error */);
 }
});

resolve и reject - это функции. resolve необходимо вызывать при успешном выполнении необходимого асинхронного кода, а reject следует вызывать только в случае возникновения каких-либо исключений или проблем. Оба resolve и reject принимают аргументы, которые затем передаются в функции .then и .catch соответственно.

promise.then((result) => {
 console.log(result);
},
(error) => {
 console.log(error);
});
// you could handle the errors by passing it in .catch instead of .then as well
promise.catch((error) => { console.log(error); }); 

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

Promise.all(/* arrayOfPromises */).then((values) => {
\\ values is array of resolved promise values from arrayOfPromises
},(error) => {
\\ if any of the promises in arrayOfPromises fails we reach here
});

Он принимает массив обещаний в качестве аргумента и возвращает обещание, которое разрешается после разрешения всех обещаний в переданном массиве и отклоняется, если какое-либо из обещаний в массиве отклоняется.

Здесь я попытался интегрировать обещания с обратными вызовами, не меняя структуру ajaxRequest функции. Обещания для запросов ajax для всех супергероев были созданы и собраны в массиве с именем superPromises. Promise.all метод, который ожидает выполнения всех переданных ему обещаний и возвращает одно обещание, также был интегрирован. Таким образом, пока все обещания в superPromises не будут выполнены, completedFetchingData не будет выполняться. Здесь все запросы ajax могут выполняться асинхронно. Функции разрешения и отклонения выполняются в обратном вызове и, следовательно, выполняются только после завершения соответствующего запроса ajax.

Если ajaxRequest не работает, функция обратного вызова вызывается с ошибкой в ​​качестве параметра, и обещание отклоняется. Если одно из обещаний в superPromises не выполняется, то обещание, возвращаемое Promise.all, также отклоняется. Следовательно, обработка ошибок также выполняется легко.

Асинхронный / Ожидание

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

const someAsyncFn = async (param) => {
    const result = await someOtherAsyncFn(param);
    return result;
}

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

Здесь я отказался от обратного вызова и переместил обещание на возврат функции ajaxRequestwithPromise. Я сделал это, потому что этот IMO - правильный способ использования обещаний, а также потому, что функция async / await не работает с функциями с обратными вызовами, поскольку они предназначены для возврата / получения обещаний, а не для обратных вызовов.

Я создал async функцию с именем request и сделал в ней запросы ajax для каждого из superHeroes.. Ключевое слово await, добавленное перед вызовом функции ajaxRequestWithPromise, заставляет выполнение кода ожидать разрешения ajaxRequestWithPromise перед повторением цикла. Таким образом, порядок, в котором передаются superHeroes, является порядком извлечения данных. Если вы хотите, чтобы все данные superHeroes извлекались асинхронно, вам следует переключиться на метод Promise.all, о котором я упоминал выше.

Если запрос ajax терпит неудачу, то возвращенное обещание отклоняется с ошибкой, и обещание включающей async request функции также отклоняется и может быть обработано с помощью функций .then или .catch.

Таким образом, в этой статье я проиллюстрировал несколько моментов, почему использование promise и async/await более предпочтительно по сравнению с традиционными обратными вызовами в долгосрочной перспективе. Сообщите мне в комментариях, если вы категорически не согласны со мной и думаете, что я ошибаюсь, или если вы считаете, что эта статья вам каким-либо образом помогла. Вот суть, когда все фрагменты кода собраны в одном месте. Также вот несколько ссылок, которые мне понравились, чтобы начать работу с обещаниями и асинхронными функциями.