Уморихте ли се да пишете сложни функции за обратно извикване и да се занимавате с „пирамидата на гибелта“? Ако е така, тогава е време да научите за обещанията в JavaScript!

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

В тази статия ще разгледате по-отблизо какво представляват обещанията, как да ги използвате и защо са предпочитани пред обратните извиквания. Така че вземете чаша кафе и нека се потопим!

Какво е обещание?

Концепцията за обещания може да бъде разбрана по-добре чрез пример.

Представете си, че сте дете. Майка ти е обещала да купи шоколад днес.

Майка ти е обещала, че ще ти купи шоколадови бонбони. Така че това е обещание. JavaScript обещанията работят по същия начин, както обещанията работят в реалния живот.

Ето как бихме написали сцената в JavaScript.

const promise = momBuysChocolate();

momBuysChcocloate е обещание.

Майка ти още не ти е купила шоколадите. В JavaScript казваме, че обещанието е pending. Можем да проверим това, като влезем promise в конзолата.

Ако майка ви купи шоколада, ние казваме, че обещанието е resolved. Когато обещанието е resolved, вие изпълнявате следващите стъпки в повикването then.

momBuysChocolate()
  .then("eat it") 

Но ако майка ви не ви купи шоколадите, ние казваме, че обещанието е rejected. Когато дадено обещание е rejected, изпълнявате своя резервен план в повикването catch.

momBuysChocolate()
  .then("eat it")
  .catch("start crying") 

В javascript ние боравим с данни, когато използваме обещания. Ако обещанието е разрешено, ние правим нещо с данните. Когато обещанието бъде отхвърлено, ние обработваме грешката.

getSomeDataWithPromise()
  .then((data) => {/* Process the data */})
  .catch((err) => {/* Handle the error */})

Сега знаете какво е обещание. Нека да видим как да съставим обещание.

Създаване на обещание

Можете да създадете обещание, като използвате new promise. Конструкторът на обещание приема два аргумента resolve и reject.

const promise = new promise((resolve, reject) => {
  //Do something here
})

Ако обещанието бъде разрешено, то ще премине през повикването then. Параметърът, който подавате в resolve, ще бъде аргументът, който получавате в извикването then.

const promise = new promise((resolve, reject) => {
  resolve("Hello")
})

promise.then((text) => console.log(text))
//Hello

Ако обещанието бъде отхвърлено, то ще премине през повикването catch. Параметърът, който предавате на отказ, ще бъде аргументът, който получавате в извикването catch.

const promise = new promise((resolve, reject) => {
  reject("Error")
})

promise.catch((text) => console.log(text))
//Error

Както можете да видите resolve и reject са само функции за обратно извикване.

Нека практикуваме наученото и да се опитаме да конструираме momBuysChocolate.

Знаеш, че майка ти е обещала да ти купи шоколадови бонбони. Така че нека започнем със създаването на празно обещание.

const momBuysChocolate = new promise((resolve, reject) => {
  //We will do something here
})

Майка ти каза, че ще ти купи шоколада по-късно тази вечер. Нека се опитаме да стимулираме това с setTimeout. Вместо да чакаме до вечерта, ще изчакаме пет секунди.

const momBuysChocolate = new promise((resolve, reject) => {
  setTimeout(()=>{
    // Will Mom bring choclcaltes?
  }, 5000)
})

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

const momBuysChocolate = (isMomHappy) => {
  return new Promise((resolve, reject) => {
    setTimeout(()=>{
      if (isMomHappy) {
        resolve('Got Chocolates'); // fulfilled
      }else{
        reject('mom is not happy'); // reject
      }
    }, 5000)
  })
}

Нека тестваме обещанието. Ако console.log изпълните обещанието си, трябва да го видите чакащо за 5 секунди. Ще видите също Got Chocolates или mom is not happy в зависимост от това, което сте задали за isMomHappy

const promise = momBuysChocolate(true)
  .then(text => console.log(text))
  .catch(error => console.log(error))

Не беше толкова трудно да се създаде обещание, нали?

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

Защо имаме нужда от обещания?

Има три основни причини, поради които разработчиците предпочитат обещания пред обратни извиквания.

  1. Обещанията намаляват вложения код.
  2. Обещанията позволяват обработка на всички грешки веднъж в края.
  3. По-лесно е да визуализирате потока на кода.

Нека видим всички тези предимства в действие. Ще напишем JavaScript код, който извършва асинхронна работа както с обратни извиквания, така и с обещания.

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

Нека направим това стъпка по стъпка. Първо ще получим заявката от предния край. Обикновено ще направите заявка за публикация за това.

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

app.post("/send-message", (req, res) => {
  //Charge the customer
})

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

Така че кодът ще изглежда така.

app.post("/send-message", (req, res) => {
  customer = req.body

  //Charge the customer
  chargeCustomer(customer, (err, charge)=>{
      if(err) return err

      //Add it to database
  })
})

Нека преминем към код, базиран на обещания. По същия начин искате да таксувате клиента. Ако таксувате клиента успешно, вие ще го добавите към базата данни в повикването then. Ако таксуването не успее, можете да се справите с това в разговора catch.

app.post("/send-message", (req, res) => {
  customer = req.body

  //Charge the customer
  chargeCustomer(customer)
    .then({/* Add it to database */})
    .catch(err => console.log(err))
})

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

Базираният на обратно извикване код би изглеждал така

app.post("/send-message", (req, res) => {
  customer = req.body

  chargeCustomer(customer, (err, charge)=>{
      if(err) return err

      addToDatabase(customer, (err, document) => {
          if(err) return err

          //Send message to recepient
      })
  })
})

За код, базиран на обещание, ако операцията с базата данни е успешна, вие ще изпратите съобщението до получателя в повикването then. Ако операцията с базата данни е неуспешна, можете да я обработите в последното catch извикване.

app.post("/send-message", (req, res) => {
  customer = req.body

  chargeCustomer(customer)
    .then(() => addToDatabase(customer))
    .then({/* Send message to recepient */})
    .catch(err => console.log(err))
})

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

Ето как би изглеждал кодът, базиран на обратно извикване

app.post("/send-message", (req, res) => {
  customer = req.body

  //Charge the customer
  chargeCustomer(customer, (err, charge)=>{
      if(err) return err

      addToDatabase(customer, (err, document) => {
          if(err) return err

          sendMessage(customer, (err, message) => {
              if(err) return err

              //Informs the frontend, everything went well.
              res.send("Success")
          }
      })
  })
})

И ето как би изглеждал базираният на обещание код

app.post("/send-message", (req, res) => {
  customer = req.body

  //Charge the customer
  chargeCustomer(customer)
    .then(() => addToDatabase(customer))
    .then(() => sendMessage(customer))
    .then(() => res.send("Success"))
    .catch(err => console.log(err))
})

Можете ли да видите колко лесно е да напишете асинхронен код с обещания вместо обратни извиквания? Преминаване от ада за обратно извикване към по-лесен за четене код.

Почисти

В обещанието има и клауза finally. Той се изпълнява, когато обещанието е изпълнено: независимо дали resolve или reject.

Основната цел на finally е да извърши почистване след приключване на операциите. Например затваряне на връзки, спиране на индикаторите за зареждане и др.

const promise = momBuysChocolate(true)
  .finally(() => console.log("Perform Cleanup")) // Gets triggered first
  .then(text => console.log(text))
  .catch(error => console.log(error))


//Output
Perform Cleanup
Got Chocolates

Изпълнение на множество обещания наведнъж

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

За да използвате Promise.all, предайте масив от обещания, които искате да изчакате. Ако всички обещания бъдат разрешени, ще се премине към повикването then. Аргументът на then call ще бъде масив от резултати от всички обещания.

Дори ако едно от обещанията бъде отхвърлено, то ще премине към повикването catch.

const cookMeal = promise.all([
  startStovePromise,
  GetUtensilsPromise,
  DecideRecipePromise])
    .then([stove, utensils, recipe] => {
        console.log(`Started Stove`)
        console.log(`Got Utensils`)
        console.log(`Decided Recipe`)
    })

Обобщавайки

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

Ако това ви е харесало, моля, разгледайте и мояблог.

Повече съдържание в PlainEnglish.io.

Регистрирайте се за нашия безплатен седмичен бюлетин. Следвайте ни в Twitter, LinkedIn, YouTube и Discord.

Изградете осведоменост и възприемане на вашия технологичен стартъп с Circuit.