Галоўная / Артыкулы

Await бачыць толькі адну абяцку: проектаванне API на базе Async/Await, якіе самі виканаюць свою роботу

Што на самай працо спостерагае JavaScript async/await, чаму аддзеяныя заданні, калебэкі, стрымы і Promise.all ўхіляюцца ад яго, і як разрабатваць Promises, якія будуць абяроŭнаваць завершэнне задання.

Выдавец: REACTAPP.TOP13 хв чытання3003 слоў

Крыніцы: 90% of Developers Don’t Understand What JavaScript `await` Actually Waits For

Легкаа ўважаць await за „чакаць, пакуль функцыя завершыцца“, але рэальны час выканання дае значна жорсткія абавянанні: ён вырахоўвае адну выразу, спрацоўвае рэзультат як значэнне на кшталт Promise і призупіняе дзейнасць тэкучай async функцыі, пакуль гэтае значэнне не будзе готава. Усё інша залежыць ад таго, як было створанае гэтае значэнне. Гэты разлік адказвае на пытанне, чаму код з акуратна размешанымі ключавымі словамі await можа ўсё ж такі вярнуцца занадта рана, працаваць медленней, чым трэба, ігнораваць адказы на бяды чы працаваць з застарэлымі дадзеннямі. Працаваўшы з прыкладамі нижэй, вы зможаце адразу бачыць будзь-які await і задаць аднае важлівае пытанне: чы рэальна гэты Promise выражае ту работу, якая мяне цікавіць?

Проблема: код, які чакае на await, выглядае завершаным, але такім не ёсць

Багі ў гэтым роцэ паўтараюць аднаковы патэрн. Разработчык чакае на функцыю і прыпускае, што всё, што ўсунула гэта функцыя, вучынена. Ён чакае на метод масіва і прыпускае, што кожны асінхронны калбэк выкананы. Ён чакае на налашчэнне таймера, падпіску на запуск западчыні або запіс у стрым і верыць, што асновная операцыя завершылася. Код выглядае правільна, таму што await знаходзіцца тут.

Але await не мае жаданага уявлення пра „заданне“, „запрос“ або „всё, што запусціла гэта функцыя“. Якщо выраз, які знаходзіцца правая ад яго стороны, не пакрывае весь жыцёвы цикл работы, нічыя частка гэтага ключавага слова не можа пакрыць яго. Урахоўваючы гэта, змініцца спосаб проектавання API, ціклоў, фонавых заданняў, коду, які працуе на западчыні, і абрабаткі памылак.

Выклік выкананы ранейш за чаканне

Возьмім самы звычайны рядок у асінхронным JavaScript.

const user = await loadUser();

Часы выконання не спачатку бачають await, а потым ввічліва праскаюць loadUser запрацаваць асінхронна. Яны адэкватна ацэнююць loadUser() так сама, як і будзь-які іншы вызов функцыі. Шырок гэтага вызову даее цэню, якая можа быть натыўным Promise, спецыяльным thenable, звычайным об’ектам, стрэлкам, undefined або чым-небудзь іншым. Ляч таго await прыначынае працаваць з рэзультатам.

Паслядкам гэтага ёсць тое, што сама аперацыя зазвычай пачынаецца ў момент вызову, перш чым пачынаецца чаканне. Якщо вы вызываеце два асінхронных функцыі і зберагаеце ў сабе ўсе ўсіх іх Promises прычым не чакаеце на рэзультат жаднай з іх, обе аперацыі пачынаюцца практычна адночасна:

const userPromise = loadUser();
const settingsPromise = loadSettings();

const user = await userPromise;
const settings = await settingsPromise;

Хутчэй loadUser() пачынае сваю роботу і вяртае Promise, пасля чаго loadSettings() пачынае сваю. Две лініі await проста збіраюць рэзультаты, калі тыя стаюць доступнымі.

Пораўняйце гэта з версіяй, якую большасць людзей пішае за замовчаннем:

const user = await loadUser();
const settings = await loadSettings();

Тепер loadSettings() нават не выклікаецца пакуль першая Promise не будзе выкарыстоўвана і функцыя не будзе продовжыцца. Две запыткі выкананыя адна за іншай.

Заўважыце, што сам await не прымеў ніякага рашэння ў паўтарозным выкананні. Рашэнні прымала пазыцыя вызоваў функцый. Кораткі спосаб зрозумець гэта: вызов асінхроннай функцыі пачынае аперацыю; await вяршыць, калі чынная функцыя будзе продаваць сваю роботу. Адна аўтарызацыя ў першым патэрне: якщо settingsPromise будзе адхіліцца, калі вы яшчэ чакаеце на userPromise, гэта адхіленне на час застанеся без адпаведнага адгуку, што дзеякія среды выканання спраўляюць як адказку. Promise.all узбегае гэтай проблемы, стежычы за обомі Promises адразу. Чырвоныя паўтарыць гэтыя варыянты можна знайсці ў стацыі пра выбір между Promise.all, Promise.race і асэквэнцыйнымі await.

Вырашэнне аднаго Promise, а не кожнага пабочнага эфекту

Разглядзім функцыю стварэння абанку, яка зберагае запис, апрашоўвае електраняўку з прывітаннем без чакання або ўзврату яе, а пасля вяртае абанак.

async function createAccount(input) {
  const account = await accountRepository.save(input);

  sendWelcomeEmail(account.email);
  return account;
}

Потым абонент пісае яўную фразу:

const account = await createAccount(input);

Этот вызываючы элемент запускаецца, калі Promise, які вяртае createAccount, выпанаваецца, тое ўсё пасля таго, як завершыцца збережэнне і функцыя вярнуцца. Ён нічога не паведамляе пра электронную пашту. sendWelcomeEmail стварыў аддзельную асінхронную операцыю, чыя Promise ніколі не была прыяўленая да той, яку вяртае createAccount, таму з точкі зору вызываючага элемента такая робота не існуе.

У гэтым і крыўця непрацоўкі. await следуе толькі за ланцугам Promises, доступным з значэння, якое яму было даўна. Ён не скануе все, што запускала функцыя, і абавесці не чакае, пакуль процес стане неактыўным.

Практычныя наследкі ў трох аспектах:

  • Якщо Promise з электронной паштой будзе адхілена, try/catch вызываючага элемента ніколі яго не пазнае.
  • Якщо процес будзе завершаны, электронны лист можа проста ніколи не быць аправленым.
  • Якщо працоўнік HTTP адпавядае з успехам, гэты успех нічога надзеянага не сказвае пра задачу, яка выканана аддзельна.
  • Нічыя з гэтых параграфаў не значыць, што кожны побачны эфект павінен завершыцца ў межах запиту. Большая частка роботы законна належыць да фонавага выканання. Асэнсіяйныя проблемы — цэла неяснае апрацоўванне. Робота, якая мае значэнне, павінна быць вернутая і чаканая, чынна пазначаная і адносаваная як неабавязковая, або пераданая ў стойкія чергі чы сістэмы задач. Стварэнне Promise і проста яго заставаць без увагі — гэта не фонавая обробка; гэта проста надзея.

    Функцыя async можа завершыцца, калі яе падфункцыі яшчэ працуюць

    Адзін з супадзялых выкладакоў — гэта тое, што ключавая слова async змушвае JavaScript стежыць за кожной асінхронной операцыяй, якая запускаецца ўнутрь функцыі. Аднак гэта не так. Функцыя async стварае роўна адну Promise, і гэтая Promise зв’язана толькі з тым, што функцыя явна вяртае або чакае ў межах своего контрольнага прайску. Усё інша можа продавацься пасля таго, як гэтая Promise будзе вырашана.

    Класычны прыклад выкорыстоўвае forEach:

    async function processUsers(users) {
      users.forEach(async (user) => {
        await updateUser(user);
      });
    }
    

    Кожны раз, калі вызываецца асінхронны калебэк, вяртаецца Promise, але forEach проста відмовляецца ад гэтых значэнняў. Ён не скупляе іх, не чакае на іх і не прадстаўляе іх; ён вяртае undefined. Таму processUsers практычна мгнова доходзіць да свайго заканчальнага складання, і адтолькі, што async функцыя, якая завершылася, рэзалюецца з undefined, вызываючы элемент продовжае сваю роботу задоўга прычыні таго, што жаданая адчынка яшчэ не завершылася:

    await processUsers(users);
    console.log("All users processed");
    

    Ця запісь проста-такі проста некароткі. Апелянт чакаў, калі processUsers завершыць планаванне калебаў, а не калі калебы выконаюць сваю роботу.

    Ёй выправіць трэба падключыць дзецячыя Promise-ы да роднай операцыі. Залежна ад ситуацыі гэта можа значыць викорыстанне цыклу for...of з await усередине для строгага парадку, Promise.all(users.map(updateUser)) для повнага паралелізму, або лімітар канкурэнціі, калі колекцыя можа быць настолькі большай, што перавантажыць базу дадзеных чы апі. Шырэйшая правіла распрастраńваецца далей, чым толькі forEach: родная Promise чакае толькі на роботу дзецячых Promise-аў, якія ўжо ў ўсёй ланцюговай структуре. Такія інструменты, як правіла no-floating-promises і no-misused-promises у typescript-eslint, автаматычна выловляюць багато з такіх памылак.

    API калебаў невізыбельныя для await

    Значны частка экасыстэмы JavaScript існавала ўчора за Promises і выкарыстоўвала калбэкі для абавесці пра завершэння. Чырва гэта прыводзіць да такога коду:

    await setTimeout(() => {
      console.log("Timer completed");
    }, 1000);
    

    Ён не зупініцца ні на секунду. setTimeout рэгіструе калебэк і негайна вяртае хэндл таймера (чысло ў браузерах, об’ект Timeout у Node.js). эты хэндл не ўтварае Promise, які б пазначаў момент запуску таймера. await прымеў звычную значэнне, спрацоўвае яе як вялікі ўжо і продаўжывае выконанне функцыі на наступным мікратаске, задоўга перш чым запрацюе калебэк.

    Тая ж проблема выступае з абераннікамі змаганняў, старэйшымі API файловай системы, драйверамі баз дадзенаў, стрімамі і самастраўненымі SDK-амі. Якщо функцыя вяртае падпіску, хэндл запита, булева значэнне або емітэра змаганняў, чаканне на гэтае значэнне не ператварае калебэк у завершэнне Promise.

    Ёк хочаце чакаць на выконанне калебэка, вам патрэбен Promise, які будзе рашыцца або адхіліцца самэлькі тады, калі калебэк паведаміць пра рэзультат:

    function delay(milliseconds) {
      return new Promise((resolve) => {
        setTimeout(resolve, milliseconds);
      });
    }
    
    await delay(1000);
    

    Ця чаканая значэнне насправды прадставляе сабою запуск таго заходу, які вас цікавіць: Promise застаецца у стане чакання пакуль таймер не вызове метод resolve. Сучасныя платформы часта вже маюць такія кэпсулі; напрыклад, Node.js працюе з Promise у функцыі setTimeout з рэгістрацыяй у модуле node:timers/promises і за дапамою util.promisify для функцый у стылі калебэкаў.

    Гэта адкрывае пытанне працэса проектавання, якое стоіць за кожным await: чы выданае значэнне прадставляе завершэнне задачы, чы проста ее рэгістрацыю, запуск або кантроллер для задачы, якая яшчэ выконваецца? Якщо гэта адна з апошніх варыянтаў, чаканне на яе не прынесе таго рэзультата, які вы сабе уявляеце.

    Thenables, звычныя значэнні і межа мікрзадач

    await не выклікае наявнасць справжней інстанцыі Promise. Якщо значэнне ўособліваеся як thenable, тыяму, об’ект з можлівасцю вызвання метода .then, await викорыстоўвае механізм выкарыстоўвання гэтага об’екта. Самэ гэта дазволяе бібліятэкам, сумесным з Promise, і спецыяльным асінхронным об’ектам працаваць безпосередна ў функціях async, а таксама значыць, што об’ект можа дзеяць асінхронна, не выкарыстоўваючы new Promise.

    Для значэнняў, якія не ўособліваюцца як thenable, await усё равно дадае асінхронны крок:

    async function example() {
      console.log("before");
      await 42;
      console.log("after");
    }
    
    console.log("start");
    example();
    console.log("end");
    

    Этот код выведае start, before, end, а толькі пасля таго after, нават як 42 быў доступны адразу. Функцыя выдае значэнне на рэверсі await, сінхронны код пасля вызову продовжваее работу, а рэштка example выконваецца пазней як мікрозадача.

    Такім чынам, ачакванне звычайнага значэння не ўскладнюе процес сінхроннай роботы. Яно можа змяніць порядак выконання вашага коду ў стосунку да суседніх сінхронных заповедзей і іншых мікрозадач у черзі, а ў сценарыях, дзе важлівы порядак выконання, непатрэбны await стварае перакрыцча ў графіку выконання. У спецыфікаціях працывоспрыямлення такая затрымка зазвычай є незначной, але канцэпцыйнае значэнне застаецца: код пасля await ніколі не продовжваее работу на той жа неперарваны стак вызоваў.

    Адхіленне ў складзе процеса выкарыстоўвання

    Людзі частаўка кажуць, што await чакае, пакуль Promise будзе вырашаны. Болей тачна сказаць, што ён чакае, пакуль Promise будзе задоволены, а задоволенне включае таксама адхіленне. Калі Promise, які чакаюць, адхіляецца, await выкідае прычыну адхілення ўсередине текущай async функцыі, і самэ гэта дазволяе звычным try/catch конструкцыям працаваць у разе асінхронных адхіленняў:

    try {
      const order = await loadOrder(orderId);
      return order;
    } catch (error) {
      logger.error(error);
      throw error;
    }
    

    Гэта захопчыца дыяўаць можа толькі тады, калі абмовлены Promise насправды чакаецца ўнутрь блока try. Promise, створаны і застаўшыся без дальнейшага выкарыстання, цэлкам выйшае за межы гэтай захопчыцы:

    try {
      loadOrder(orderId);
    } catch (error) {
      console.error("This does not catch the Promise rejection");
    }
    

    Ёнклэў вызову сам павярнуўся успешна: ён вярнуў Promise без выклікання адхілення. Адхіленне прыходзіць пазней, через той Promise, пасля таго, як блок try ўжо давно завершыўся.

    Гэта ўзымае ўзвышаныя рызыкі, калі await нарочна не выкалікаецца, каб чыгунка стала „неблокіруючай“, тады калі разрабоўка яшчэ вырачоўвае, што апавярнутыя try/catch елементы яе захаваюць. Аднак гэта не так. Кожная асінхронная операцыя патрабуе чыстага власніка для сваіх няудач: вызывача, які яе чакае або вяртае, явнага адпаведніка .catch або стойкай системы, якая бярэ на сябе адпаведальнасць. Promise без власніка — гэта не шырэйшая архітектура; гэта шлях выключэння з бага, які не падключаецца да коду, які розумее, што пашло не так. Стаття на що насправды гарантуе async/await розглядае тую ж ідею власніка з точкі зору обробкі бягоў.

    Promise.all координуе рэзультаты, а не вынікі

    Promise.all зазвычай апрацоўваецца як "чаканне на калькі Promise-а адночасна". Гэта прыемная апрыксія, але важныя деталі. Ён вяртае новы Promise, які выпалняецца, калі выпалняюцца ўсе вхідныя элементы, і абрабатваецца як невыпалняючыся, як толькі хоць адны з іх абрабатваецца як невыпалняючыся.

    const results = await Promise.all(tasks);
    

    Нават тут await мае адзін ціль — аграгатны Promise, які стварыў Promise.all.

    Калі якая-небудзь з задач зазнае неудачы, аграгатны Promise таксама зазнае неудачы, і ваша функцыя праходзіць па шляху адпаведнай памылкі. Рэшта задач не анулююцца; яны продовжаюць выконванне і можа ўсё ўсё ўзмоцніць побачныя эфекты пасля таго, як вызывач вже заявіў пра неудачу пакета. Уявіце сабе пакет, які запускае калькі зовнішніх апдэйтаў: адны з яных негайна зазнае неудачы, вызывач паведамляе "пакет зазнаў неудачы", а іншыя апдэйты тыхо успеваюць, у результате чаго система застаецца часткова змененай.

    Promise.all дае вам можлівасць коордынацыі рэзультатаў, але не транзакцый чы анулювання. Выберыце інструмент, які падходзіць пад неабходную семантыку:

    • Якщо частковая успехоўнасць ўзяць за можлівую, зберагуйце кожны рэзультат (напрыклад, за дапамогою Promise.allSettled) і перагляньце кожны з яных.
    • Якщо вам патрэбна модель «всё або нічога», адміністрацыя аперацыяў трэба здольная да справжняга анулювання або механізма компенсацыі.
    • Якщо застатковая работа павінна зупініцца пасля першай неудачы, кожная аперацыя павінна падтрымляць анулювання, зазвычай за дапамою спяльнага AbortSignal.

    Складаны Promise не можа анулюваць работу, яка ўжо пачалася. Яго выкарыстанне означае толькі тое, што дазволяе яго структура.

    Стрымы і ресурсы, якія працуюць довга

    Стрымы ўсё таксама часта ствараюць плутанне. Метод write стрыму можа вернуць булева значэнне, якое паказвае, чы можа внутрэшні буфер прыняць ўсё больш дадзеных. Выклік .pipe() вяртае стрым-пункт падачы. Парсер, які працуе на адбыванняя зместоў, можа вернуць рэзультат як толькі будуць падключеныя яго слухачы. Чаканне на будзь-якое з гэтых значэнняў не означае чаканне, пакы стрым не завершыць работу, не вычысцыць буфер, не закрыцца і не запісае кожны байт.

    Завершэнне зазвычай сигналізуецца за дапамою такіх з’яво, як finish, end, close або error, і каб надзеявацца на яго надзеямо, гэтыя з’явы павінны быць ператвораныя ў Promise. Багатыя бібліятэкі, а таксама сам Node.js за дапамою pipeline у node:stream/promises, апранаюць дапаможнікі, які ствараюць адзін Promise, чыя рэалізацыя охопляе весь пайплайн, укладаючы ў сябе таксама ўсія ягоіяі.

    Гэты прынцып паўсюдна прыменяецца да будзь-якага ресурсу з дзейнасцю на длігі час:

    • Пачатак аплоаду — гэта не яго завершэнне.
    • Ачынэнне з’яноўкі — гэта не завершэнне запыту.
    • Планаванне задання — гэта не яго выкананне.
    • Даўжанне прыемлівання паведамлення — гэта не яго обробка.

    Значэнне, якое вяртае API, вялічы, што можа спазіраваць await. Перш чым даваць доверлівасць await, выявіце, якія межы жыццёвага цыклу на самай працэ ўсё-такі пазначае ягоў Promise.

    Завершэнне — гэта не рэлевантнасць

    Уявіце полье пошуку, дзе корыстнік вводзіць „JavaScript“, а потым быстра зменяе яго на „TypeScript“. Выплываюць два запиты. Запит на TypeScript вяртаецца першы, і на экране паказваюцца правильныя рэзультаты; пасля чаго прыходзіць медленнейшы адказ на JavaScript і заменяе іх.

    Кожная функцыя ў гэтым потоку правільна викорыстоўвала await. Блаканьце існуе таму, што чаканне і рэлевантнасць — гэта разныя аспекты. await працюе далей, калі Promise вырашваецца; ён не пераканваецца, чыясць хтось яшчэ хочаць рэзультат, чы не з’явіўся новышы запит, чы компонент тепер паказвае іншы запис. Код, які выглядае як последовны, толькі вырашвае параду крокаў у межах адной выклікання, а калькі выклікання той самой функцыі все ўсё можа свабодна перакрывацца.

    Для рашэння гэтага праблемы неабходна чыстая канцэпцыя пра тое, якае аператыя ў дзеянні. Вы можете абараніць застарэлыя запыты, парабяляць кожны адпаведзень з найнаветранейшым ідэнтыфікаторам запыту, выкорыстоўваць перакананні версій або паспольгавацься на слой захавання дадзэнняў, які ключуе адпаведзенні па параметрах, якія іх створылі. await не можа паведаміць, які рэзультат належыць да тэкучага стану. Для падробнага апускання, спрямованага на React, адзірніце як практычна раскінуць проблемы race conditions, якія не можна раскінуць за дапамою debouncing.

    Тое, што запыт выкананы, не значыць, што система ў стацыонарным стане

    Запіс у базе дадзеных можа завершыцца успешна, нават калі копіі яшчэ не адгукнуліся. Публікацыя паведамлення зазвычай вырашваецца тады, калі брокер яго прыме, а не тады, калі яго обработаў канзюмер. Адправка электранаўскага паведамлення зазвычай вырашваецца, калі прадаўцы прыме запрос, а не тады, калі яго прыме хтось. Лёгка прыняць, што await дае больш падтверджэння, чым на самай працэ API фактычна дае.

    await publishEvent(event);
    

    Гэтыя лініі могу гарантаваць, што паведамленне дася да брокера. Аднак гэта не павінна значыць, што прогназы, паведамленні чырагі пошуку нижэй у ланцоўцы таксама былі прыйняты.

    await sendEmail(message);
    

    Таксама, гэта можа значыць толькі тое, што аппаратчык прыняў паведамленне. Гэта не гарантуе яго доставку, тое, што абліквенты яго прачытаў, або нават тое, што аппаратчык яго захавае пасля пазнейшай обработкі.

    Тое, што значыць «зданае», вялічынаеся на адпаведнасці з кантрактом API, які стварае Promise, і await не можа паўнейша ўзмocніць гэты кантракт. Проблемы пачынаюцца, калі наступныя рэчы прыпускаюць, што вялікія змяны ў стане вялічынаюцяся ўжо, напрыклад, калі канцэнтрык публікуе запаведамленне і адразу ж запытае модэль чытання, якая адпавядае абліквенту, спакушаючыся, што новыя даны вялічынаюцяся там. Promise была правільна здана; проста архітэктура яшчэ не адгукнулася. Залежна ад продукту, правільны дызайн можа вернуць статус «у ачакванні», чытаць з модэля запісу, чакаць на явную падтверджэння або выкарыстоўваць прынцып эвалюятывай кансістэнціі ў інтэрфейсе.

    Дызайн Promise, якія падходзяць своім назвам

    Большая частка плутання ў викорыстоўванні await зникае, калі стае звычкай ставіць аднэўсь цяперашнія пытанні: чы рэзультат, які я чакаю, апавяртае завершэнне всій операцыі, якая мяне цікавіць? Якщо рассмотрыць паказаныя раней прыклады, адказ ясна — няма, калі:

    • функцыя стварае падчыныя Promises без ўрачысткі ўвыходу або чакання ўсіх іх
    • метод масі відкидае Promises, якія вяртаюцься асінхроннымі калбэкамі
    • API вяртае об’ект керування і паведамляе пра завершэнне через запуск змаганняў
    • выпускнік рашуецца раней, чым спожывачы завершылі свою частку работы

    Нявісць await паказвае толькі тое, што текущая функцыя на час завершэння працы адпаведнага Promise-падобнага рэзультата была призупінена. Гэта не паказвае, што супаўзвязаная работа завершылася, што побачныя эфекты выйшлі толькі адназначна, што пазнейшая работа была анульвана, што бягі ўскладненняў застаюцца актуальнымі, або што рэзультат досяглі стану актуальнасці.

    Гэтая прычына таго, што правильна рэалізацыя асінхроннага коду ў большай меры ёсць проблемай дызайну API. Promise, які вяртае функцыя, должен быць задовольненым у тым моменте, які адпавядае адпаведальнасці, пра якую заявляе яго назыв. Функцыя пад называм saveDocument должна чытна паказваць, чы рэалізуе яе Promise локальную зберагачванне, даўню сінхронізацыю чы і тое, і іншае. Функцыя пад называм scheduleReport можа заканчыцца з рашэннем пасля прийняцьбы заведама, а не пасля завершэння звіту, але яе назыв і тип вяртання должны чытна паказваць гэта, напрыклад, палягаючы на рашэнні з ідэнтыфікаторам заведама, які можа выкарыстоўваць вызываючы код для перагляду чы стежэння.

    Што можа, а што не можа обяцваць await

    • await вырахоўвае адну выраз і чакае, пакуль гэтае адно значэнне будзе задовольнена; ён нічога не ведае пра „заданне“ як цэласць.
  • Месца, дзе вы вызываеце асінхронныя функцыі, вялікай меры ўплываюць на тое, чы робота будзе выканана паралельна; await вялікай меры ўплывае толькі на момент продазваю.
  • Робота, якая не вяртаецца і не чакаецца, ўтрымліваецца окола: яе адказы, час выканання і завершэння залишаюцца невідомымі для таго, хто яе вызваў.
  • API для калбэкаў, западзеў і стрымоў павінны быть загорнутыя ў Promises, якія будуць завершаны рэальна, каб await мела сэнс.
  • Promise.all не анулюе і не вяртае стан ранейшага стану; выберыце allSettled, компенсацыю або AbortSignal залежна ад сэмантіки, якой вы патрабуеце.
  • Завершэння, релевантнасць і адпаведнасць — гэта тры разныя гарантіі, і толькі першая даецца за дапамогою await.
  • Спрыяйце кожнаму Promise, які вы вяртаеце, як кантракту, і нехай яго завершэнне значыць самэ тое, што парадкавае назва функцыі.
  • Супаўзяныя матэрыялы

  • Серыяванне асінхронных задаўнікаў у JavaScript з абявай пра обмежэнне паўтарэнняў: Ўжывайце ланцуг абяваў AsyncLock, каб запыты кліента, якія выкананы адночасна, выкананы былі за прынцыпам FIFO, і стан UI застаўся арганізаваным без викорыстання вбудованых блакітаў нитак.