Въведение

Javascript е език за програмиране с една нишка, поддържащ асинхронно изпълнение, за да отговори на нуждите от едновременно изпълнение, без да блокира основната нишка. Javascript обещанията са чудесен начин за справяне с асинхронното изпълнение.

Обещанията съдържат евентуалния резултат от асинхронна операция. Например, ако достигнем някой REST API, можем да използваме обещания, за да получим евентуалния резултат от този API.

Обещанието може да бъде във всеки от посочените по-долу три етапа:

  1. В изчакване: Чака се резултат
  2. Изпълнено:Обещанието води до успех
  3. Отхвърлено: Обещанието води до неуспех

Концепция 1: Как да създадете обещание и да го консумирате

Нека вземем асинхронна функция, както е дадено по-долу

function async_fn() {
  setTimeout(function(){
    console.log("executed");
  }, 1000);
}

Сега, ако искаме да върнем нещо от async_fn, имаме две опции:

  1. Използвайте функция за обратно извикване
  2. Използвайте обещание

Недостатъкът на първия подход е, че той води до „адски проблем с обратно извикване“.

Сега нека да видим как този проблем може да бъде разрешен с помощта на обещания. За да създадем обещание, използваме дадения по-долу синтаксис

new Promise((resolve, reject)=>{
  if(success){
    resolve("success");
  } else {
    reject("failed");
  }
});

Пример:

function timer() {
  return new Promise((resolve, reject)=> {
    setTimeout(()=>{
      resolve("Timeout");
    },2000);
  });
}

Promise предоставя три метода, за консумиране на обещание и обработка на резултата

  1. тогава:ако обещанието доведе до успех, се извиква обратно извикване на метода then
  2. уловка:ако обещанието е неуспешно, се извиква обратно извикване на catch метод
  3. накрая:обратно извикване на метода, извиквано, когато обещанието е изпълнено успешно или неуспешно. Може да се използва за обичайна задача като скриване на товарача и др.

Сега нека видим как да консумираме обещание в следния пример:

timer().then((response)=>{
  // function returned in success
  console.log(response);
}).catch((error)=>{
  // function returned in error
  console.log(error);
}).finally(()=>{
  // always called
  console.log("Promise completed");
});

Концепция 2: Оковаване на обещание

then метод на обещание връща ново уредено обещание, което може да се използва за по-нататъшно верижно свързване. Да приемем сценарий, при който искате да използвате множество обратни извиквания за дадено обещание и да обработите резултата един по един във формат на конвейер, т.е. резултатът от едно обратно извикване да се използва в други. Това може да се осъществи с помощта на техниката на верижно свързване.

Верижното свързване може да се реализира по два начина.

  1. Използване на индивидуален манипулатор на грешки за всяко успешно обратно извикване:
    then методът приема два аргумента, единият за успешно обратно извикване, а другият е за неуспешно обратно извикване. Както е дадено в примера по-долу:
timer().then(successHandler1, failureHandler1).
  .then(successHandler2, failureHandler2)
  .then(successHandler3, failuerHandler3)
  .then(successHandler4, failureHandler4)

Забележка: Грешките, възникнали в предишния successHandler, ще бъдат предадени на следващия failureHandler обратно извикване, напр. грешка, хвърлена в successHandler1, ще бъде уловена в failureHandler2 и следващите манипулатори на грешки.

2. Използване на манипулатор на обща грешка: Тъй като обратното извикване failureHandler не е задължително в метода then, можем да предадем само successHandler в метода then и да използваме блок catch за обработка на обща грешка, както е дадено в примера по-долу

timer().then(successHandler1)
  .then(successHandler2)
  .then(successHandler3)
  .catch(errorHandler)

Вижте примерния код по-долу:

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

Ако throw error от някое от then обратно извикване, тогава за следващите и следващите then методи error обратното извикване ще бъде изпълнено и накрая анализираният error ще бъде предаден на последния catch блок

Концепция 3: Обработване на множество обещания заедно

Понякога е необходимо да се обработят две или повече обещания и манипулатори за изпълнение, когато тези обещания бъдат разрешени. Сега има два начина да постигнете това:

  1. Изчакайте всички обещания да бъдат разрешени: за да постигнете този обект Promise, осигурете all метод, който взема едно или повече обещания като масив и връща обещание, което се разрешава, когато всички от тях доведат до успех или някое от тях провалят се. Вижте синтаксиса по-долу
Promise.all([promise1, promise2, promise3])
    .then(response => {
      // response is array contains result of promises passed
     }).catch(error => {
      // only first error will be passed i.e. if two promise result in error then first error will be passed to error callback
     })

Всички обещания ще бъдат изпълнени без зачитане на техните резултати при успех или неуспех, например, ако promise2 доведе до неуспех, тогава promise3 ще бъде изпълнено, но catch манипулаторът ще бъде извикан незабавно и грешка от promise2 ще бъде предадена на catch блок и then блок никога няма да бъде изпълнен

Вижте примерната програма по-долу:

2. Изчакайте поне едно обещание (от списък с обещания) да бъде разрешено: Ако искаме да разрешим родителско обещание веднага щом някое от дъщерните обещания бъде разрешено, можем да използваме метода race, наличен в обекта Promise. Вижте синтаксиса по-долу:

Promise.race([promise1, promise2, promise3])
    .then(response => {
      // response contains result of first promise resolved
     }).catch(error => {
      // error contains error of first promise resolved
     })

Вижте примерната програма по-долу:

Концепция 4: Async & Await

Async-await е начин, който ви позволява да извиквате асинхронни функции по синхронен начин, което води до по-чист код. Така че нека се потопим.

Към момента знаем, че асинхронните функции са функции, които преминават в чакащо състояние при повикване и изчакват някакъв резултат и разрешават успех или неуспех в зависимост от резултата от изпълнението. Така че сега приемете, че трябва да управлявате две или повече такива функции и резултатът от първата функция трябва да бъде предаден на втората функция. Един от начините да се справите с това е като използвате втората функция вътре в then обратно извикване на първата функция, но това ще направи кода по-сложен за разбиране. Тук идва async-await за спасяване. Да видим как.

Да приемем, че имаме две асинхронни функции, getManagerByEmployeeId, getManagerNameById.

getManagerByEmployeeId: Взема employeeId като вход и връща отчетен мениджърски идентификатор.

getManagerNameById:Взема managerId като вход и връща името на мениджъра.

Сега нашата задача е да получим името на отчетния мениджър на дадения идентификатор на служител, накратко, да внедрим функцията getManagerNameв кода по-долу.

const EmployeeIDManagerIdMap = {
 "AA234": "0AA316",
 "BBCD5":"4AA354"
};
const ManagerIdManagerNameMap = {
 "0AA316":"John Doe",
 "4AA354":"Ravindram S"
};
function getManagerByEmployeeId(employeeId) {
  return new Promise((resolve, reject)=> {
    setTimeout(()=>{
      if(EmployeeIdManagerIdMap[employeeId]) {
       resolve(EmployeeIdManagerIdMap[employeeId]);
      } else {
       reject(`Invalid employee id ${employeeId}`);
      }
    },2000);
  });
}
function getManagerNameById(managerId) {
    return new Promise((resolve, reject)=> {
    setTimeout(()=>{
      if(ManagerIdManagerNameMap[managerId]) {
       resolve(ManagerIdManagerNameMap[managerId]);
      } else {
       reject(`Invalid manager id ${managerId}`);
      }
    },2000);
  });
}
// get Manager Name by employeeId
function getManagerName(employeeId) {
  // return manager name
}

Така че един от начините да направите това е да използвате вложена структура на обещание, както е дадено по-долу, и да конвертирате getManagerName в обещание

function getManagerName(employeeId){
  return new Promise((resolve, reject)=>{
    getManagerByEmployeeId(employeeId).then(function(managerId){
      getManagerNameById(managerId).then(function(managerName){
        resolve(managerName);
      }, function(error){
        reject(error);
      })
    }, function(error){
      reject(error);
    })
  })
}

Както можете да видите, това ще доведе до структура на вложен код, която е малко трудна за разбиране и ненужно ни принуждава да използваме обещание във функцията getManagerName.

Сега нека видим внедряването на async-await:

async function getManagerName(employeeId){
  try {
   let managerId = await getManagerByEmployeeId(employeeId);
   try {
     let managerName = await getManagerNameById(managerId);
     return managerName;
   } catch(error) {
     console.error("getManagerNameById promise rejected", error);
   }
  } catch(error){
    console.error("getManagerByEmployeeId promise rejected", error);
  }
}

И така, както можете да видите сега, кодът е опростен и не е необходимо да използваме обещание във функцията getManagerName.

Това, което await прави, е да отложи изпълнението, докато извиканата async функция не бъде разрешена и всяка функция, съдържаща await, трябва да бъде декларирана като asyncот използвайки asyncключовата дума, както правим в горния код. Освен това отхвърленото обещание хвърля грешка при извикване на функция, следователно може да бъде обработено с помощта на блок try-catch, заобикалящ извикването на асинхронна функция.

Концепция 5: Използване на разрешени обещания

По време на модулно тестване понякога трябва да имитираме работата на действителното обещание и да го използваме за тестване. Можем да направим това, като използваме вече разрешени обещания, като използваме методите resolve & reject от класа Promise.

Вижте примера по-долу:

// In simillar way you can also use reject method
const getEmployeeData = Promise.resolve({"name":"Piyush", "age":"25", "ssn":"1324ssb"});
testEmployeeSuccess = function(){
  // promise is already resolved
 getEmployeeData().then(function(employeeData){
    var result = processData(employeeData); 
    if(testResult(result)){
      console.log("test OK");
    } else {
      console.log("test failed");
    }
 });
}

Заключение

Това са петте основни концепции за обещания, които обсъдихме, а именно.

  1. синтаксис
  2. верижно свързване
  3. обработка на множество обещания заедно
  4. асинхронно изчакване
  5. предварително решени обещания

За да научите повече за обещанията, можете да разгледате в MDN документи. в връзката по-долу



Благодаря, че прочетохте статията ми, ако искате някакви редакции или актуализации в статията или имате някакви предложения, уведомете ме в секцията за коментари. Благодаря отново, приятно кодиране. :)