Все началось с асинхронного программирования. Как справедливо сказал Платон, Необходимость - мать всех Изобретений, чтобы справиться с непредсказуемой природой асинхронных программ, было рождено Обещание! (Гром, молния и большие приливные волны на заднем плане. :P)

Простите мое драматическое вступление. Я хотел сделать обещания интересными.

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

Что такое асинхронное программирование?
Асинхронное программирование — это способ, который позволяет вашей программе запускать потенциально длительную задачу и по-прежнему реагировать на другие события во время выполнения этой задачи, а не ждать, пока эта задача завершится. законченный. Как только эта задача будет завершена, ваша программа будет представлена ​​с результатом.

function delay(time) {
  setTimeout(executeAfterDelay, time);
}

function executeAfterDelay() {
   console.log("Print after delay");
}

function asyncCall() {
  console.log("Calling delay function");
  delay(2000);
  console.log("After delay function");
}

asyncCall();
//Console log Results:
 //Calling delay function
 //After delay function
 //Print after delay (after a delay of 2 seconds)

Когда вызывается функция asyncCall, выполняется первая строка внутри нее, затем вызывается функция задержки, но Javascript не ждет, пока она не будет полностью выполнена, переходит к следующей строке, затем, когда функция задержки выполняется, функция обратного вызова, связанная с тайм-аут выполняется.

Следующий очевидный вопрос: что такое обратные вызовы?
обратные вызовы могут показаться сложными, но они не что иное, как функции, которые передаются в качестве аргументов другим функциям.
Пример 1:

Пример 2:

Но проблема с обратными вызовами заключается в том, что они никогда не могут возвращать значения или выдавать ошибки.
Существует также проблема попадания в ад обратных вызовов или спагетти-код.
Теперь представьте, если бы мне пришлось вызывать несколько вызовов API, где я должен ждать ответа на предыдущий вызов API, чтобы вызвать следующий вызов.
Для этого мне пришлось бы написать вложенные обратные вызовы.
Это будет выглядеть примерно так.

//First API Request
$.ajax({
  url:'endpoint/v1/request1',
  type: "GET",
  success:function(data){
    //Second API Request 
    $.ajax({
        url:'endpoint/v1/request2?id=' + data.id,
        type: "GET",
        success:function(data){
            //Third API Request
            $.ajax({
                url:'endpoint/v1/request3?name=' + data.name,
                type: "GET",
                success:function(data){
                  //do something with the data
                }
            });
        }
    });
  }
});

Мы можем избежать ада обратных вызовов, используя промисы.

ECMAScript 2015, также известный как ES6, представил объект JavaScript Promise. Промисы в Javascript — это способ обработки асинхронных операций. Это позволяет нам возвращать значение из асинхронной функции, такой как синхронные функции. Вместо немедленного возврата окончательного значения асинхронный метод возвращает обещание предоставить значение в какой-то момент в будущем.

Но что такое обещание?

Обещание — это объект, который может находиться в одном из трех состояний:
1. Ожидание: Исходное состояние.
2. Решено/Выполнено: завершено успешно
3. Отклонено: сбой.

Говорят, что обещание выполнено, когда оно либо выполнено, либо отклонено. При возникновении любой из этих опций будет выполнен метод «тогда» или метод «поймать», связанный с обработчиком обещания.

Функции «тогда» и «поймать» принимают функцию/обратный вызов в качестве параметра, когда обещание выполнено или отклонено соответственно.

Чтобы объяснить, как работает обещание, давайте возьмем пример с «fetch». Метод fetch() современный и универсальный, поэтому мы начнем с него. Он не поддерживается старыми браузерами, но очень хорошо поддерживается современными.

Основной синтаксис:

let promise = fetch("url/v1/endpoint");

поэтому fetch возвращает обещание, Javascript не ждет, пока обещание не будет выполнено. Он переходит на следующую строку. Но когда обещание разрешено/отклонено, мы можем что-то с ним сделать.

fetch("url/v1/endpoint")
.then(data => console.log(data))
.catch(err => console.err(err));

Если вы заглянете в документацию fetch, то заметите, что данные, возвращаемые fetch, разрешаются с помощью объекта встроенного класса Response.
Нам нужно разобрать объект ответа как JSON (мы можем анализировать во многие другие форматы, такие как текст, formData, blob и т. д., но для простоты я использую JSON)
для этого нам нужно использовать response.json(data). Это также возвращает промис.
Промисы хороши тем, что теперь мы можем связывать промисы в единую цепочку, в отличие от обратных вызовов. Если какое-либо обещание будет отклонено, оно перейдет к функции catch.

fetch("url/v1/endpoint")
//Resolving fetch() promise
.then((data) => { 
  return data.json()
})
//Resolving data.json() promise.
.then((jsonData) => console.log(jsonData.someKey));
//Catch reject from all the promises above.
.catch(err => console.err(err));

Угадайте, что!
Вы также можете создать свой собственный объект Promise!! Давайте рассмотрим это. Если вы веб-разработчик, скорее всего, вы не сталкиваетесь с созданием объектов промисов так часто, но это поможет нам немного лучше понять промисы.

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

Мы можем сделать это с помощью функции setTimeout().

function setup(){
  noCanvas();
  setTimeout(saySomething, 5000);
}

function saySomething() {
   createParagraph("SOMETHINGGGGG");
}  

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

function setup(){
  noCanvas();
  // prints SOMETHINGGGGG after 5 seconds
  delay(5000)
  .then(() => saySomething())
  .catch(err) => console.error(err));

// promise is rejected and console log shows Delay time needs to be a number.
  delay('xxx')
  .then(() => saySomething())
  .catch(err) => console.error(err));

}

function delay(time) {
  return new Promise((resolve, reject) => {
      if(isNaN(time)) {
        reject(new Error('Delay time needs to be a number.'));
      }else{
        setTimeout(resolve, time);
      }
  });
}

function saySomething() {
   createParagraph("SOMETHINGGGGG");
}

Я предполагаю, что я хоть чуть-чуть помог вам с обещаниями. Давайте рассмотрим еще несколько концепций, связанных с обещаниями, таких как статические методы Promise.all и Promise.race.

Promise.all
Допустим, есть ситуация, когда вам нужно дождаться успешного разрешения всех промисов, чтобы что-то с ними сделать. Вы можете сделать это с помощью Promise.all.

Promise.all принимает несколько объектов обещаний в качестве аргументов и возвращает обещание. Это обещание разрешается только тогда, когда разрешаются все обещания, данные в массиве. Полученные данные представляют собой массив результатов всех разрешенных промисов.

const promise = Promise.all([promise1, promise2, promise3]);
promise.then((allData) => {
   allData.forEach((data) => {
       console.log(data);
   });
});

Обратите внимание, что Promise.all имеет быстрое поведение при сбое. Если какое-либо из промисов отклонено, то Promise.all будет отклонен в нужный момент и не будет продолжать разрешать следующие промисы в очереди.

let p1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 1000, 'p1');
});

let p2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 1200, 'p3');
});

let p3 = new Promise(function(resolve, reject) {
    setTimeout(reject, 300, 'p4');
});

let p4 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 800, 'p5');
});

let promise = Promise.all([p1, p2, p3, p4]);
promise.then(function(data) {
    data.forEach(function(data) {
        console.log(data);
    });
})
.catch(function(error) {
    console.error('error', error);
});

//Console logs error p4 
//Reason: p3 is the one with least time out and gets rejected first. 
//so it does not try to resolve all other promises.

Если вы хотите продолжать, даже если несколько обещаний отклонено, то вы можете иметь подвох для каждого из них в отдельности.

let promise = Promise.all([p1.catch(function() {}), 
  p2.catch(function() {}), 
  p3.catch(function() {}), 
  p4.catch(function() {})
]);

promise.then(function(data) {
    data.forEach(function(data) {
        console.log(data);
    });
})
.catch(function(error) {
    console.error('error', error);
});

//Console logs:
//p1
//p2
//undefined
//p4

Если в массиве есть обещание, которое должно вернуть другое обещание, выполнив другой запрос API или что-то еще, вы можете использовать .then на обещании, из которого вы хотите вернуть другое обещание.

Рассмотрим сценарий:
Пользовательские данные, которые разбросаны по нескольким базам данных (db1, db2, db3), есть одна основная база данных с информацией о том, какая база данных содержит информацию о каком пользователе.
db1, d2 , базы данных db3 содержат идентификатор, имя пользователя, информацию об электронной почте.
Хотя существует еще одна база данных Arch, которая содержит информацию о профиле пользователя, такую ​​как имя, фамилия, адрес, страна, почтовый индекс и т. д.

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

export type user {
    id:number,
    username:string,
    email:string,
    firstname:string,
    lastname:string,
    address:string,
    country:string,
    postalCode:string,
}

Давайте напишем для него решение, используя все знания, которые мы знаем о Promise.all.

const main = require('./main');
const db1 = require('./db1');
const db2 = require('./db2');
const db3 = require('./db3');
const arch = require('./arch');
   
module.exports = function(id) {
  const dbs = {
    db1, db2, db3,
  };
  
  const promise = Promise.all([
    main(id)
    .catch(() => {  //individual reject handling
      return Promise.reject("Error Main")
    })
    .then((db) => {  //Individual resolves can also be handled
      return dbs[db](id); //Returns a new promise.
    })
    .catch(() => {  //individual reject handling for dbs[db](id) promise
      return Promise.reject('Error ' + db);
    }),
    arch(id)
    .catch(() => {  //individual reject handling
      return Promise.reject("Error Arch")
    })
  ]);

  return promise
    .then((data) => ({
      id: id,
      username: data[0].username,
      email: data[0].country,
      firstname: data[1].firstname,
      lastname: data[1].lastname,
      address:data[1].address,
      country: data[1].country,
      postalCode: data[1].postalCode
    }))
    .catch((err) => {
      console.log(err)
      throw err;
    })

};

Я думаю, что этого было достаточно для Promise.all.

Давайте перейдем к Promise.race.
Это несколько противоположно Promise.all.
Promise.race принимает массив обещаний. Результатом является новое обещание, которое разрешается или отклоняется, как только одно из обещаний разрешается или отклоняется.

function delay(time) {
  return new Promise((resolve, reject) => {
      setTimeout(resolve,time, 'Success ' + time);
  });
}

Promise.race(delay(1000), delay(100))
.then((data) => console.log(data) )
.catch((err) => console.error(err));

//console log will show: Success 100
//As that was the first promise which was resolved.

Далее давайте посмотрим на async/await, который стал частью ES8 в 2017 году.
async/await — не новая функция. Это новый способ написать асинхронную функцию, которая возвращает обещание.

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

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

function delay(time) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve("resolved"), time);
  });
}

async function asyncCall() {
  console.log('calling');
  const result = await delay(2000);
  console.log(result);
  // Expected output: "resolved"
}

asyncCall();
//Console log prints "resolved" after 2 seconds

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

Давайте вернемся к примеру с выборкой, который я использовал в начале промисов, и заставим его работать с асинхронным ожиданием.

function setup() {
  getData()
  .then((data) => console.log(data))
  .catch((err) => console.error(err));
}

async function getData() {
  const data = await fetch("url/v1/endpoint");
  const jsonData = await data.json();
  return jsonData;
}

Чтобы лучше понять async/await, давайте попробуем повторить пример вызовов jQuery ajax API, который я использовал при объяснении обратных вызовов с использованием async и await и fetch.

async function getResult(){
  const response1 = await fetch("endpoint/v1/request1");
  const response2 = await fetch("endpoint/v1/request2?id="+response1.id);
  const response3 = await fetch("endpoint/v1/request3?name="+response2.name);
  return {
      id:response1.id,
      name:response2.name,
      address:response3.address
  };
}

function printResult() {
  getResult()
  .then((data) => console.log(data))
  .catch((err) => console.error(err));
}

Посмотрите, каким плоским, лаконичным и простым выглядит код, если сравнить его с эпохой обратного вызова. Это также становится легко понять.
Я надеюсь, что этим блогом я немного облегчил вам жизнь.
Я пытаюсь разобраться во многих других концепциях javascript и надеюсь написать о них.
Пожалуйста, оставляйте мне свои ценные комментарии и предложения, они обязательно помогут в моих поисках.