Вокруг нас происходят синхронные и асинхронные действия. Можете ли вы вспомнить примеры синхронных действий, которые вы выполняете ежедневно?
– Принятие душа
– Сон
А как насчет асинхронных действий?
– Приготовление пищи
– Просмотр телевизора
Все мы слышали фразу: “Нельзя служить двум господам одновременно”. Именно это и означает синхронность. Вы должны делать что-то одно за один раз. Чаще всего характер работы не позволяет вам делать что-то еще.
Простой и понятный вид деятельности, который мы все выполняем, – это принятие душа. Вы когда-нибудь пробовали принимать душ и готовить одновременно? Не пытайтесь, ничем хорошим это не закончится. Вы когда-нибудь пробовали совершать пробежку во время сна? Скорее всего, нет.
Это повседневные примеры синхронных действий, которые мы все выполняем.
А как насчет асинхронных действий? Просмотр телевизора – это асинхронная деятельность. Вы можете смотреть телевизор и есть пиццу. Вы можете смотреть телевизор и спать. Приготовление пищи – это тоже асинхронная деятельность. Если рис еще не готов, это не значит, что вы не можете позвонить по телефону или помыть посуду. Вы можете заниматься чем-то другим, пока рис готовится. Это то, что мы подразумеваем под асинхронными задачами. Вы можете заниматься другими делами, пока асинхронная задача выполняется.
Теперь давайте рассмотрим, зачем нам нужен асинхронный код. Разве мы не можем просто писать код синхронно и идти своей дорогой? Ну, да, мы можем. Давайте рассмотрим пример.
console.log("Step 1");
for(let i = 0; i <= 9; i++){
console.log(i)
}
console.log("Step 2")
Код JavaScript выполняется сверху вниз. Код выполняется построчно, по порядку.
Так, в нашем примере будет выполнена первая строка, которая выведет в консоль “Шаг 1”. Затем начнет выполняться цикл for, который выведет числа от 0 до 9. Когда эта операция будет завершена, начнет выполняться следующая строка и выведет на консоль “Шаг 2”.
Как видите, цикл for сначала должен быть выполнен полностью, прежде чем будет запущена следующая строка кода. Теперь посмотрите на тот же пример, но на этот раз мы увеличим диапазон чисел.
console.log("Step one")
for(let i = 0; i < 1000000;i++){
console.log(i)
}
console.log("Step 2")
Скорее всего, вы будете ждать некоторое время. По мере выполнения цикла вкладка браузера будет не реагировать, пока цикл не завершится. Основной процесс JavaScript блокируется синхронным циклом. Синхронный код также называют блокирующим кодом.
Ваш браузер закатит истерику, когда долго выполняющийся процесс заблокирует основной поток.
Вы можете видеть, как это может стать проблематичным. Как же решить эту проблему? С помощью асинхронного кода, который также называют неблокирующим кодом.
JavaScript предоставляет три основных способа написания неблокирующего кода.
- Обратные вызовы
- Обещания
- Асинхронность/ожидание
Обратные вызовы
Обратный вызов – это функция, которая передается другой функции в качестве параметра. Давайте рассмотрим пример встроенной функции JavaScript, которая принимает в качестве параметра другую функцию.
setTimeout(()=>{
console.log("Some asynchronous action")
},1000)
Функция setTimeout будет ждать заданное количество миллисекунд, а затем вызовет свою функцию обратного вызова. Обратные вызовы долгое время были де-факто способом обработки асинхронных операций в JavaScript.
Затем появился ES6, в котором появились обещания. Мы рассмотрим обещания в следующем разделе. Проблема с обратными вызовами заключается в том, что по мере увеличения количества асинхронных операций увеличивается и количество обратных вызовов. Это называется “ад обратных вызовов”. Давайте рассмотрим пример.
const stepOne = () => {
console.log("Step 1");
};
const stepTwo = () => {
console.log("Step 2");
};
const stepThree = () => {
console.log("Step 3");
};
const stepFour = () => {
console.log("Step 4");
};
setTimeout(() => {
stepOne();
setTimeout(() => {
stepTwo();
setTimeout(() => {
stepThree();
setTimeout(() => {
stepFour();
}, 2000);
}, 3000);
}, 2000);
}, 1000);
По мере увеличения количества асинхронных операций ваш код становится труднее читать.
Обещания были введены в ES6 как современное решение для обработки асинхронного кода в JavaScript.
Обещания
Обещания были введены в ES6 (ECMAScript 2015) как современное решение для обработки асинхронных операций. Обещания состоят из 3 состояний:
- Ожидание
- Выполнено
- Отклонено
Когда обещание находится в состоянии ожидания, оно еще не выполнено и не отклонено. Как следует из названия, операция еще не выполнена.
Когда операция будет завершена, обещание вернет одно из двух состояний. Оно будет либо разрешено, либо отклонено. Если обещание разрешится, это означает, что операция прошла успешно и мы можем получить доступ к значению обещания. Если произошла ошибка, обещание будет отклонено с ошибкой. Давайте рассмотрим пример.
const marvelOrDC = (answer) =>{
// Let's start by capitalizing the user input
answer = answer.toUpperCase()
// This is how we return a promise
return new Promise((resolve,reject)=>{
if(answer === 'MARVEL'){
resolve("You are a Marvel comics fan")
}else if(answer === "DC"){
resolve("You are a DC comics fan")
}
// If the user inputs anything other than Marvel or DC the promise will be rejected
else{
reject("You need to either pick marvel or DC")
}
})
}
marvelOrDC("")
Как же мы получаем доступ к значению, которое возвращает обещание? Мы вызываем метод .then() нашего обещания.
marvelOrDC("").then(data=>{
console.log(data)
},(err)=>console.log(err))
Метод .then() принимает два параметра. Первый – это функция обратного вызова, которая будет вызвана, когда обещание разрешится. Второй – функция обратного вызова, которая будет вызвана, если обещание будет отклонено.
Но есть более чистый способ обработки отказов – использование метода .catch().
marvelOrDC("")
.then(data=>{
console.log(data)
})
.catch(err=>console.log(err))
Теперь, когда у нас есть представление о том, как писать обещания, мы можем приступить к рассмотрению async/await, которые используют обещания под капотом.
Синтаксис Async/Await был представлен в ES2017
Async/Await
Синтаксис async/await – это синтаксический сахар для написания обещаний. Это более чистый и менее многословный способ работы с обещаниями. При работе с обещаниями с помощью async/await нам не нужно выстраивать цепочки методов .then() или .catch(). Давайте перепишем предыдущий пример с использованием async/await, чтобы понять, что я имею в виду.
const marvelOrDC = (answer) =>{
// Let's start by capitalizing the user input
answer = answer.toUpperCase()
// This is how we return a promise
return new Promise((resolve,reject)=>{
if(answer === 'MARVEL'){
resolve("You are a Marvel comics fan")
}else if(answer === "DC"){
resolve("You are a DC comics fan")
}
// If the user inputs anything other than Marvel or DC the promise will be rejected
else{
reject("You need to either pick marvel or DC")
}
})
}
const handlePromise = async(answer) =>{
try {
const result = await marvelOrDC(answer)
console.log(result)
} catch (error) {
console.log(error)
}
}
handlePromise("")
Наша функция handlePromise помечена ключевым словом async. Это позволяет функции обрабатывать обещания, а также облегчает использование ключевого слова await внутри функции.
Блок try/catch используется для обработки различных ответов, которые возвращает обещание. Ключевое слово await приостанавливает выполнение до тех пор, пока обещание не будет либо разрешено, либо отклонено. Если обещание разрешено, то результат будет записан в журнал. Если обещание отвергнуто, то в журнал будет занесена ошибка.
Заключение
Нам удалось охватить много информации. Так что спасибо вам. Надеюсь, это послужит вам ступенькой на пути к освоению JavaScript.