Что такое функция обратного вызова JavaScript?

Функция обратного вызова — это не что иное, как функция, которая передается в другую функцию в качестве параметра. Давайте лучше разберемся с приведенным ниже примером:

const animal = species => {
 console.log(`The species is ${species}`) // 2nd console
};
const dog = func => {
 console.log("You have called the function dog") // 1st console
 func("dog")
 console.log("Above line is from callback function") // 3rd console
};
dog(animal);

Вышеупомянутая функция вернет —

You have called the function dog
The species is dog
Above line is from callback function

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

Но зачем нужен обратный вызов?

Обратные вызовы введены для выполнения асинхронных действий в JavaScript. Функция всегда ожидает завершения выполнения функции обратного вызова перед выполнением кода после обратного вызова. В приведенном выше примере функция dog должна была дождаться завершения работы функции animal перед фиксацией третьей консоли. Обратные вызовы гарантируют, что функция не будет запущена до завершения определенной задачи.
Обратите внимание на setTimeout: setTimeout принимает функцию обратного вызова для выполнения через определенное время. Например:

const test = () => {
 console.log('Function executed')
}
setTimeout(test,10000)

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

Обещания

Чтобы объяснить, что такое промис в JavaScript, вместо того, чтобы переходить непосредственно к определению, давайте возьмем пример. Допустим, ваш друг ОБЕЩАЕТ вам устроить вечеринку, теперь из этого будет только два исхода — либо он Устроит вам вечеринку, либо НЕ Устроит вам вечеринку, при этом второй сценарий более вероятен :)

Из приведенного выше утверждения мы понимаем, что будет только два исхода: Успех или Неудача. Точно так же обещания в JavaScript также используются для выполнения определенного блока кода, а затем возвращают результат, который будет либо успешным, либо неудачным.

Определение обещания и работа

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

const promiseExample = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('this is simple promise example');
  }, 300);
});

console.log(promiseExample);

Обещание всегда находится в одном из трех состояний:
- ожидание: начальное состояние
- выполнено: операция успешно завершена
- сбой: операция не удалась

В промисе любое из вышеперечисленных состояний обрабатывается .then(), который является методом промиса. .then() принимает два аргумента: первый аргумент — это функция обратного вызова для обработки выполненного состояния, а второй аргумент — функция обратного вызова для отклоненного состояния. В промисах состояния успеха и неудачи обрабатываются функциями resolve() и reject() соответственно. При выполнении асинхронного вызова функция resolve() используется для обработки успеха асинхронной операции, а reject() используется для обработки сбоя операции.

Цепочка обещаний

Цепочка необходима для обработки последовательных асинхронных операций. Это достигается с помощью .then(), который после выполнения кода возвращает другое обещание, которое затем снова может быть связано с другим .then().

const promiseExample = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Resolve this promise');
  }, 300);
});

promiseExample
  .then(handleResolvedA, handleRejectedA)
  .then(handleResolvedB, handleRejectedB)
  .then(handleResolvedC, handleRejectedC);

В приведенном выше фрагменте каждый .then() возвращает новое обещание, которое будет обрабатываться следующим .then() . Тем не менее, что, если есть ошибка, и мы хотим, чтобы пользователь знал об ошибке, а также не хотим выполнять последующую .then()?

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

doSomething()
 .then(() => throw new Error())
 .then(() => console.log('does not print and call moved to catch'))
 .catch(() => console.log('Prints the error'))

Даже после .catch() мы можем добавить еще один .then(), так как catch также возвращает новое обещание, в котором был обработан сбой. Следующий then/catch будет выполняться только в том случае, если предыдущая функция завершила выполнение. Выполнение цепочки всегда синхронно.

Но зачем нам нужен Promise, если у нас уже есть функции обратного вызова?

Ответ действительно прост, чтобы избежать сценария ада обратного вызова. Ад обратного вызова — это сценарий, в котором есть несколько вызовов обратного вызова, что делает код нечитаемым, а также трудным для понимания. Именно тогда были введены промисы для создания и разработки более читабельного и легкого для понимания кода.

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

Async/Await — это специальный синтаксис, который был введен для более модной работы с обещаниями.

Асинхронные функции

Асинхронные функции выглядят как простые функции с ключевым словом async. Ниже приведен пример того, как выглядит функция:

async function() {
 return "Hello World!!!"
}
// ES6
const example = async() => {
 return "Hello World!!!"
}

Асинхронная функция возвращает обещание, в котором выполнение всегда выполняется внутри resolve method. Это означает, что если мы будем использовать асинхронную функцию с промисами, то вызов всегда будет идти на .then() вместо .catch().

async function example() {
 return "Hello World"
}
example().then((data) => console.log(data))

Ждите

Await похож на Promise.then(), что означает, что await обрабатывает возвращенный объект обещания. Ожидание всегда будет ждать, пока обещание не будет выполнено, а затем возобновится с результатом обещания. Если какой-либо объект доступен, то есть если он поддерживает .then(), то он определенно будет поддерживать ожидание

async function example() {
 const promiseExample = new Promise((resolve,reject) => {
  resolve("Hello World")
 }
 await promiseExample
}

Как обрабатывать ошибки?

Так же, как в Promise у нас было .catch() , в async/await мы используем блок try/catch для обработки ошибок.

try {
 await fetch('/api-call')
 console.log('Print Success')
} catch(error){
  console.log('Print Error')
}

Зачем async/await, если у нас уже есть промисы для обработки асинхронных операций?

Async/Await — это просто синтаксический сахар Promise, но использовать async/await действительно полезно по следующим причинам:

  • Более читаемый код
  • Легко понять, так как сложно понять сложную цепочку в промисах.
  • Экономьте больше памяти, так как await не останавливает выполнение кода до тех пор, пока не будет выполнено обещание, код выполняется и не ждет, пока await вернет результат. JavaScript может выполнять другие операции параллельно.
  • Обработка ошибок и условная обработка лучше.

Заключение

В приведенном выше обсуждении мы прочитали краткое описание обратных вызовов, промисов и async/await. Мы также обсудили различия между ними и то, почему они были введены. Мы также видели пример для каждого из них, чтобы получить представление о том, как выглядит синтаксис.