Асинхронен JavaScript: Обяснени са обратно извикване и обещания

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

Обратните обаждания бяха единственият естествено поддържан начин за работа с асинхронния код до 2016 г., когато Promiseобектът беше въведен в езика. Разработчиците на JavaScript обаче са внедрявали подобна функционалност в собствените си години, преди да се появят обещания. Нека да разгледаме някои от разликите между обратно извикване и обещания и да видим как се справяме с координирането на множество обещания.

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

// Можете да дефинирате обратното си обаждане отделно ...

нека myCallback = () => {

  console.log ('Обаден!');

};

setTimeout (myCallback, 3000);

// ... но често се срещат и обратни извиквания, дефинирани вградени

setTimeout (() => {

  console.log ('Обаден!');

}, 3000);

Обикновено функцията, която приема обратно повикване, го приема като последен аргумент. Това не е случаят по-горе, така че нека се преструваме, че има нова функция, наречена, waitкоято е точно като, setTimeoutно приема първите два аргумента в обратен ред:

// Бихме използвали новата си функция по следния начин:

waitCallback (3000, () => {

  console.log ('Обаден!');

});

Вложени обратни обаждания и пирамидата на гибелта

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

// Бихме използвали новата си функция по следния начин:

waitCallback (2000, () => {

  console.log ('Първо обратно обаждане!');

  waitCallback (3000, () => {

    console.log ('Втори обратен разговор!');

    waitCallback (4000, () => {

      console.log („Трето обратно извикване!“);

    });

  });

});

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

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

По-лесно асинхронизиране с обещания

Обещанията предоставят по-гъвкав API за справяне с асинхронни задачи. Той изисква функцията да бъде написана така, че да връща Promiseобект, който има някои стандартни функции за обработка на последващо поведение и координиране на множество обещания. Ако нашата waitCallbackфункция беше Promiseбазирана, щеше да отнеме само един аргумент, което е милисекундите за изчакване. Всяка следваща функционалност ще бъде свързана с обещанието. Първият ни пример ще изглежда така:

нека myHandler = () => {

  console.log ('Обаден!');

};

waitPromise (3000) .после (myHandler);

В горния пример waitPromise(3000)връща Promiseобект, който има някои методи, които да използваме, като then. Ако искахме да изпълняваме няколко асинхронни функции една след друга, бихме могли да избегнем пирамидата на обречеността, като използваме обещания. Този код, пренаписан в подкрепа на новото ни обещание, ще изглежда така:

// Без значение колко последователни асинхронни задачи имаме, ние никога не правим пирамидата.

waitPromise (2000)

  .после (() => {

    console.log ('Първо обратно обаждане!');

    връщане waitPromise (3000);

  })

  .после (() => {

    console.log ('Втори обратен разговор!');

    връщане waitPromise (4000);

  })

  .после (() => {

    console.log ('Втори обратен разговор!');

    връщане waitPromise (4000);

  });

Още по-добре, ако трябва да координираме асинхронни задачи, които поддържат Promises, бихме могли да използваме all, което е статичен метод за Promiseобекта, който приема няколко обещания и ги комбинира в едно. Това би изглеждало така:

Promise.all ([

  waitPromise (2000),

  waitPromise (3000),

  waitPromise (4000)

]). then (() => console.log ('Всичко е направено!'));

Следващата седмица ще разгледаме по-нататък как обещанията работят и как да ги използваме идиоматично. Ако просто изучавате JavaScript или се интересувате от тестване на знанията си, опитайте се waitCallbackили се опитайте да постигнете еквивалента на Promise.allобратните обаждания.

Както винаги, свържете се с мен в Twitter с коментари или въпроси.