Прежде всего, я расскажу вам, что такое 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.

С уважением!