Прежде всего, я расскажу вам, что такое JavaScript.
Javascript — это высокоуровневый язык программирования. Это мультипарадигмальный язык, который поддерживает несколько парадигм программирования, включая процедурное, объектно-ориентированное и функциональное программирование. Одним из важных аспектов JS является его управление событиями.
Что касается среды выполнения, JavaScript обычно выполняется в однопоточном режиме. Это означает, что он обрабатывает одну задачу за раз, последовательно, без одновременного выполнения. Однако JavaScript также предоставляет такие функции, как асинхронное программирование с использованием обратных вызовов, promises и async/await, которые позволяют разработчикам для обработки задач, выполнение которых может занять больше времени, не блокируя выполнение другого кода.
Что такое обратный вызов в JS?
Обратный вызов — это функция, которую вы можете передать другой функции как аргумент, и она выполняется после завершения любой операции.
Ниже вы можете найти несколько примеров использования обратных вызовов:
// Sync: no pass arguments in callback function const names = ['Sofia', 'Carmen', 'Julia']; const modifyArray = (arr, callback) => { // do something... arr.push('Alex'); // after doing something... callback(); } // Print in console: The array have just modified! modifyArray(names, () => { console.log('The array have just modified!'); }); // Sync: pass arguments in callback function const ages = [4, 33, 1]; const modifyArray = (arr, callback) => { // do something... arr.push(37); // after doing something... callback(arr); } // Print in console: [4, 33, 1, 37] modifyArray(ages, (array) => { console.log(array); }); // Async const sum = (a, b, callback) => { setTimeout(() => { const result = a + b; callback(result); }, 1000); } sum(2, 3, (res) => { console.log(`Callback return: ${res}`); });
Это интересно, потому что мы вызываем DDBB и должны ждать ответа, но у обратного вызова есть проблема, известная как ад обратного вызова.
Ниже вы можете найти глупый пример ада обратного вызова:
functionA(function(resultFromFunctionA) { functionB(function(resultFromFunctionB) { functionC(function(resultFromFunctionC) { functionD(function(resultFromFunctionD) { functionE(function(resultFromFunctionE) { functionF(function(resultFromFunctionF) { console.log(resultFromFunctionF); }) }) }) }) }) });
После того, как мы узнали, что такое обратные вызовы, давайте перейдем к обсуждению Promises, поскольку Promises были представлены в стандарте ECMAScript 6 (ES6), выпущенном в 2015 году. Промисы были введены для улучшения удобочитаемости и управления асинхронным кодом по сравнению с обратными вызовами, как упоминалось ранее. Начиная с ES6, промисы стали более распространенным способом обработки асинхронных операций в JavaScript. Они обеспечили более ясный синтаксис и позволили улучшить обработку ошибок и управление потоком.
Далее я покажу вам несколько примеров использования промисов:
// function that return a promise only with resolve const functionName = () => { return new Promise(resolve => { setTimeout(() => { resolve('Ale'); }, 3000); }); } // function to call above function and print the result functionName(). then(response => console.log(response)); /* First, the promise is pending until the 3 seconds indicated in the setTimeout have passed Promise {<pending>} Then print: Ale */
Теперь давайте посмотрим на более полный пример промисов. Теперь посмотрим, как он может генерировать исключение:
const exampleMethod = () => { return new Promise((resolve, reject) => { setTimeout(() => { let randomNumber = Math.round(Math.random()*10); if(randomNumber%2===0) { resolve(`${randomNumber}, it is an even number`); } else { reject(new Error(`${randomNumber}, it is not an even number`)); } },1000); }); } // function that handle the result exampleMethod() .then(res => console.log(res)) .catch(err => console.log(err)) .finally(() => console.log('We can always use the finally clause whenever we want.')); // If the result is OK, this will print: /* Promise {<pending>} <-- that is not printed 8, it is an even number We can always use the finally clause whenever we want. */ // If the result is not OK, this will print: /* We can always use the 'finally' clause whenever we want. Promise {<pending>} <-- that is not printed Error: 5, it is not an even number at <anonymous>:9:16 */
Если мы посмотрим на предыдущий пример, то сможем сделать несколько выводов.
Во-первых, все, что мы возвращаем функцией 'resolve', является ожидаемым результатом при выполнении обещания и будет обработано в '.then'. функция.
Во-вторых, все, что мы возвращаем с помощью функции 'reject', является результатом возникшей ошибки, и эта ошибка будет обработана в '.catch. функция strong>'.
В-третьих, мы всегда можем добавить предложение «finally», чтобы гарантировать его выполнение в любом случае.
Наконец, если мы наблюдаем порядок, в котором сообщения консоли выводятся до конца, выведите то, что находится внутри предложения «наконец».
Позже, с появлением ES8 (2017), была введена концепция async/await, которая представляет собой более простой и лаконичный синтаксис для работы с Promises. Async/await дополнительно упрощает обработку асинхронных операций, позволяя коду напоминать синхронное программирование без ущерба для асинхронной природы JavaScript.
Хотя Promises и async/await получили широкое распространение и рекомендуются для обработки асинхронного кода в JavaScript, обратные вызовы по-прежнему используются в определенных контекстах и в старых версиях. библиотеки, которые не были перенесены на Promises или async/await (некоторые из них можно найти по этой ссылке). В некоторых случаях также возможно использовать обратные вызовы вместе с промисами, поскольку промисы могут быть заключены в функции обратного вызова.
В завершение я предлагаю вам несколько примеров использования async/await с Promises:
// function that return a promise const functionName = () => { return new Promise(resolve => { setTimeout(() => { resolve('Ale'); }, 3000); }); } // function that call to another function which has a promise const getName = async () => { const name = await functionName(); console.log(name); } // execute getName funcion getName(); /* The result after 3 secs is: Promise {<pending>} Ale */ // function that return a promise const exampleMethod = () => { return new Promise((resolve, reject) => { setTimeout(() => { let randomNumber = Math.round(Math.random()*10); if(randomNumber%2===0) { resolve(`${randomNumber}, it is an even number`); } else { reject(new Error(`${randomNumber}, it is not an even number`)); } },1000); }); } // function that call to another function which has a promise const functionAsync = async () => { try { const result = await exampleMethod(); console.log(result); } catch (error) { console.log(error); } finally { console.log('finally is optional'); } } // execute getName funcion functionAsync(); /* If the result after 1 sec is successful, then it will print: Promise {<pending>} <-- that is not printed 8, it is an even number finally is optional */ /* If the result after 1 sec is unsuccessful, then it will print: Promise {<pending>} <-- that is not printed Error: 1, it is not an even number at <anonymous>:9:16 finally is optional */
Еще одна важная вещь, которую нужно знать, это то, что если вы хотите сделать несколько вызовов параллельно, вы можете использовать Promise.all или Promise.allSettled.
Оба используются для этой цели, но у них есть разница.
Например, когда вы используете Promise.all и одно из обещаний не выполняется, вашим ответом будет ошибка, которую вы можете обработать в методе catch. Однако Promise.allSettled предоставляет вам ответ, содержащий дополнительную информацию, например статус промисов.
Я оставляю несколько примеров ниже, чтобы понять параллельные вызовы: Promise.all и Promise.allSettled:
Обещание.все:
// async-await y promises const getName = () => { return new Promise(resolve => { setTimeout(() => { resolve('Ale '); }, 3000); }); } const getSurname = () => { return new Promise(resolve => { setTimeout(() => { resolve('Pascual'); }, 3000); }); } const getAge = () => { return new Promise(resolve => { setTimeout(() => { resolve(37); }, 3000); }); } const getData = async () => { console.time('elapsedTime'); const [name, surname, age] = await Promise.all([getName(), getSurname(), getAge()]); console.timeEnd('elapsedTime'); console.log(`Name: ${name} - Surname: ${surname} - Age: ${age}`); } getData(); /* Promise {<pending>} elapsedTime: 3009.852294921875 ms (If the calls had not been in parallel, it would have taken more than 9 seconds). Name: Ale - Surname: Pascual - Age: 37 */ // Now, let's see when one promise is reject: const getNumber = () => { return new Promise(resolve => { setTimeout(() => { resolve(98); }, 2000); }); } const getLetter = () => { return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('Test error')); }, 3000); }); } const getData2 = async () => { try { const [number, letter, age] = await Promise.all([getNumber(), getLetter(), getAge()]); console.log(`Name: ${name} - Surname: ${surname} - Age: ${age}`); } catch (error) { console.log(error); } } getData2(); /* Promise {<pending>} Error: Test error at <anonymous>:11:14 */
Promise.allSettled: (я собираюсь использовать те же примеры выше)
// async-await y promises const getName = () => { return new Promise(resolve => { setTimeout(() => { resolve('Ale'); }, 3000); }); } const getSurname = () => { return new Promise(resolve => { setTimeout(() => { resolve('Pascual'); }, 3000); }); } const getAge = () => { return new Promise(resolve => { setTimeout(() => { resolve(37); }, 3000); }); } const getData = async () => { console.time('elapsedTime'); const [name, surname, age] = await Promise.allSettled([getName(), getSurname(), getAge()]); console.timeEnd('elapsedTime'); console.log(`Name: ${JSON.stringify(name)} - Surname: ${JSON.stringify(surname)} - Age: ${JSON.stringify(age)}`); // with Promise.allSettled return an object with status and value, therefore: console.log(`Name: ${name.value} - Surname: ${surname.value} - Age: ${age.value}`); } getData(); /* Promise {<pending>} elapsedTime: 3003.567138671875 ms Name: {"status":"fulfilled","value":"Ale "} - Surname: {"status":"fulfilled","value":"Pascual"} - Age: {"status":"fulfilled","value":37} Name: Ale - Surname: Pascual - Age: 37 */ // Now, let's see when one promise is reject: const getNumber = () => { return new Promise(resolve => { setTimeout(() => { resolve(98); }, 2000); }); } const getLetter = () => { return new Promise((resolve, reject) => { setTimeout(() => { reject('Test error letter'); }, 3000); }); } const getData2 = async () => { try { const [number, letter, age] = await Promise.allSettled([getNumber(), getLetter(), getAge()]); console.log(`Number: ${JSON.stringify(number)} - Letter: ${JSON.stringify(letter)} - Age: ${JSON.stringify(age)}`); console.log(`Number: ${number.status === 'fulfilled' ? number.value: 'Failure number'} - Letter: ${letter.status === 'fulfilled' ? letter.value: 'Failure letter'} - Age: ${age.status === 'fulfilled' ? age.value: 'Failure age'}`); } catch (error) { console.log(error); // Don't enter here, each promise has a status fullfilled or rejected } } getData2(); /* Promise {<pending>} Number: {"status":"fulfilled","value":98} - Letter: {"status":"rejected","reason":"Test error letter"} - Age: {"status":"fulfilled","value":37} Number: 98 - Letter: Failure letter - Age: 37 */
Таблетка - асинхронная итерация:
//Async iteration const promises = [ new Promise(resolve => { setTimeout(() => { resolve(1); },1000); }), new Promise(resolve => { setTimeout(() => { resolve(2); },3000); }), new Promise(resolve => { setTimeout(() => { resolve(3); },5000); }) ]; async function iterateMe() { for await (const obj of promises) { console.log(obj); } } iterateMe(); /* Promise {<pending>} 1 2 3 */
Если вы зашли так далеко, я хочу поблагодарить вас и надеюсь, что это поможет вам лучше понять эту часть обратных вызовов, промисов и Async/await.
С уважением!