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

За какво е тази статия?

В тази статия ще споделя моите мисли как да се справя с асинхронните функции в колекциите на JavaScript. Ще обясня това по начина, който работи най-добре за мен, като покажа примери за код, които можете да намерите и в моето хранилище github.

Ще работим с фалшив API, който връща потребители по идентификатори, за да симулираме асинхронно извикване към истински API и въз основа на това преминаваме през няколко примера, върху които ще се съсредоточим:

  • за цикъл,
  • за всеки,
  • за … от,
  • карта,
  • филтър,
  • намалявам

За опростяване и основно фокусиране върху вашата тема всеки пример има едни и същи основни стъпки:

  • стартиране на програма,
  • започнете да измервате времето,
  • извикване на метод getUsers,
  • крайно време за измерване,
  • изпълнявам тестове,

Освен това в паралелен метод, който брои до 10, беше добавен за по-добро показване на асинхронни операции.

Да кодираме

Пример 1

Повтаряне чрез обратни извиквания с помощта на цикъл for

Тестът (ред 23) очаква да получи четирима потребители, но при изпълнение на този код (възел async-cb.js) ще бъде върната грешка. Ако регистрираме потребителите, ще излезе, че масивът е празен и това е така, защото ние просто итерираме през всички идентификатори, без да чакаме отговор. Можем да докажем това, като коментираме тест (ред 23) и добавим обратно извикване на API за влизане (ред 9 -11) и тогава виждате, че всъщност сме получили данни от нашите API, така че, както можете да видите, основният проблем е да изчакате данни от метода getUsers и във всеки следващ пример ще се опитаме да разрешим този проблем.

Пример 2

Повтаряне на обещания чрез for цикъл

Този пример съдържа още един тест (ред 22), който основно проверява формата на данните, върнати от метода getUsers. Ако изпълните този код, забележете, че този път тестовете са преминали, освен това виждате броене до 10, за да отбележите, че нашето извикване към API е асинхронно. Обобщете добавянето на обещания, които извикват вътре в цикъла for, гарантират изчакване на данни, преди да изпълнят каквато и да е операция върху тях, но какво ще стане, ако искаме да използваме масивни методи като forEach, filter, map, reduce?

Да видим какво ще стане.

Пример 3

Повтаряне на обещания чрез forEach

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

Празният потребителски масив се дължи на факта, че самият forEach не чака обратни извиквания, вместо да обикаля всички обекти, след което връща празен потребителски масив, тъй като никакъв отговор от API не е направен в този момент.

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

Преди да преминем към следващия пример, моля, опитайте да изпълните този код

Едва заменихме forEach с for…of,проверете резултата и се опитайте да си отговорите защо е така?

Пример 4

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

Благодарение на използването на картата не е нужно да декларираме потребителски масив, преди да преминем през идентификатори, сега самата карта връща потребители в съответствие с това как работи, но със сигурност? Обърнете внимание на разрешаването на всички обещания (ред 10) е задължително, много пъти липсата на това ни води до проблем, но защо е необходимо? Самата карта не връща данни, но вместо масив от обещания, тя е свързана със същия факт като в forEach, картата няма да чака, докато всяко обещание бъде бъде разрешен, той връща чакащо обещание.

Бих искал да насоча вниманието ви към времето за изпълнение на getUsers, нека го сравним с цикъла for

Откъде идва тази разлика? Всеки от този метод изпълнява четири заявки за едни и същи набори от потребителски id, за да бъдем по-точни, нашата API заявка е setTimeoutметод, който след ~500 ms (повече или по-малко, защото setTimeout гарантира, че изпълнява обратно извикване поне след 500 ms, а не точно след 500 ms, това не е тема на тази статия, така че за да получите отговора защо е това, насърчавам да се запознаете по-добре с цикъла на събития) връща потребител. Обратно към въпроса, разликата идва от факта, че картата задава и четирите заявки една след друга, без да чака всеки отговор

след това получаваме нашите потребители (Promise.all), във време, което е равно на най-дългата заявка за обещание, която в този случай е еднаква за всички заявки и е равна на ~500ms, докато for цикълът работи в по този начин:

Пример 5

Повтаряне на обещания чрез филтър

В този пример, нашата задача е да накараме само потребители, които са възрастни, като знаем как да накараме всеки потребител от нашия API, да постигнем целта си, ние трябва само да филтрираме тези потребители по възраст (ред 13). Можем да постигнем целта си по по-функционален начин, като въведем метода pipe в този пример той ще бъде pipeAsync, нека да проверим как кодът ще изглежда след промените

Пример 6

Повтаряне на обещания чрез намаляване

Сега нашата задача е да сумираме баланса на акаунта на потребителите, ако се опитате да изпълните този код, ще получите грешка, нека добавим log в ред 18, за да проверим сумата

[object Promise]40000 откъде идва? Оказва се, че стойността 40 000 е балансът на акаунта на последния потребител (можете да проверите това в lib/api.js ред 5), но защо получаваме [object Promise] , това е свързано с ключова дума за асинхронен ключ в обратното извикване на reduce (ред 6), всяка асинхронна функция връща обещание, така че в нашия случай променливата prev не е равна на конкретна стойност, но promise, за да получим действителната стойност, трябва да използваме ключовата дума await и да разрешим promise, нека направим това

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

Защо отново е над 2000ms? Изглежда, че се връщаме към тази заповед за изпълнение

и това е вярно, поради въведената променлива prevSum (ред 3), защото сега всяко обратно извикване трябва да изчака предишна стойност, за да направи заявка за API, за да коригира това само трябва да размените редове 3 и 4, нека проверим резултата

Подобрихме производителността на нашия код със 75% само чрез размяна на два реда! Това показва колко е важно да се разбира добре асинхронното програмиране.

Какво следва?

Насърчавам ви да проверите моето хранилище в github, където съм подготвил няколко упражнения за вас, за да подобрите увереността си с async/await.

Това е всичко, което исках да ви покажа, надявам се, че сте намерили нещо за вас от тази статия, а упражненията, подготвени от мен, ще ви дадат още повече!

Ако тази статия ви е харесала, моля, пляскайте с ръце, ако имате въпроси, оставете ги в коментарите или поддържайте връзка с мен в twitter!

Twitter: k_wdowik

Както винаги всякакви коментари са добре дошли!

Бележка от обикновен английски

Знаете ли, че пуснахме YouTube канал? Всеки видеоклип, който правим, ще има за цел да ви научи на нещо ново. Вижте ни, като щракнете тук и не забравяйте да се абонирате за канала 😎