Jak zabalit asynchronní JavaScriptovou funkci se slibem

Jak napsat funkci založenou na zpětném volání a poté ji převést na funkci založenou na Promise, kterou lze volat pomocí async/await.

Pro tento tutoriál použijeme full-stack JavaScriptový framework CheatCode, Joystick. Joystick spojuje rozhraní front-end UI s back-endem Node.js pro vytváření aplikací.

Pro začátek budeme chtít nainstalovat Joystick přes NPM. Před instalací se ujistěte, že používáte Node.js 16+, abyste zajistili kompatibilitu (pokud se potřebujete naučit, jak nainstalovat Node.js nebo spustit více verzí na vašem počítači, přečtěte si nejprve tento tutoriál):

Terminál

npm i -g @joystick.js/cli

Tím se Joystick nainstaluje globálně do vašeho počítače. Po instalaci vytvořte nový projekt:

Terminál

joystick create app

Po několika sekundách se zobrazí zpráva o odhlášení na cd do nového projektu a spusťte joystick start :

Terminál

cd app && joystick start

Poté by vaše aplikace měla být spuštěna a my jsme připraveni začít.

Psaní příkladu funkce založené na zpětném volání

Pro začátek napíšeme funkci, která používá tradiční (troufám si říci „starou školu“) vzor funkce zpětného volání, který byl populární před příchodem JavaScriptových slibů. V projektu, který byl pro vás právě vytvořen, když jste spustili joystick create app výše, v /lib složku, chceme přidat nový soubor sayHello.js :

/lib/sayHello.js

const sayHello = (name = '', options = {}, callback = null) => {
  setTimeout(() => {
    const greeting = `Hello, ${name}!`;
    callback(null, greeting);
  }, options?.delay);
};

export default sayHello;

Výše píšeme příklad funkce s názvem sayHello který používá vzor zpětného volání pro vrácení odpovědi, když je volána. Důvodem, proč lze použít zpětné volání, je to, že funkce, kterou voláme, potřebuje provést nějakou práci a poté reagovat později. Pomocí zpětného volání můžeme této funkci zabránit, aby blokovala JavaScript ve zpracování dalších volání ve svém zásobníku volání, zatímco čekáme na tuto odpověď.

Zde simulujeme tuto zpožděnou odpověď voláním na setTimeout() v těle naší funkce. Ten setTimeout zpoždění je dáno možnostmi, které jsme předali sayHello() když tomu říkáme. Po uplynutí této prodlevy a funkce zpětného volání časového limitu (zde je funkce šipky předána na setTimeout() ), vezmeme name předáno sayHello() a zřetězit jej do řetězce s Hello, <name> ! .

Jakmile to greeting je definován, nazýváme callback() funkce předaná jako konečný argument do sayHello předání null pro první argument (kde spotřebitel funkce očekává, že bude předána chyba – nezdokumentovaný „standard“ mezi vývojáři JavaScriptu) a naše greeting za druhé.

To je vše, co potřebujeme pro náš příklad. Pojďme lépe pochopit, jak to funguje, tím, že to použijeme a pak přejdeme ke konverzi sayHello() být založen na slibu.

Volání ukázkové funkce založené na zpětném volání

Nyní otevřeme soubor, který pro nás již byl vytvořen, když jsme spustili joystick create app výše:/ui/pages/index/index.js .

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  render: () => {
    return `
      <div>
      </div>
    `;
  },
});

export default Index;

Když tento soubor otevřete, chceme stávající obsah nahradit fragmentem uvedeným výše. Získáme tak novou komponentu Joystick, se kterou můžeme pracovat při testování sayHello() .

/ui/pages/index/index.js

import ui from '@joystick.js/ui';
import sayHello from '../../../lib/sayHello';

const Index = ui.component({
  events: {
    'click button': async (event, component) => {
      sayHello('Ryan', { delay: 3000 }, (error, response) => {
        if (error) {
          console.warn(error);
        } else {
          console.log(response);
        }
      });
    },
  },
  render: () => {
    return `
      <div>
        <button>Say Hello</button>
      </div>
    `;
  },
});

export default Index;

Když jsme to rozšířili, udělali jsme dvě věci:

  1. V řetězci HTML vráceném render() funkce ve spodní části komponenty jsme přidali <button></button> tag mezi existující <div></div> značky, na které můžeme kliknout a spustit naši funkci.
  2. Chcete-li zvládnout spouštění, těsně nad render() , přidáme events objekt a definovat posluchač události pro click událost na našem button tag.

K této definici posluchače událostí 'click button' přiřadíme funkci, která bude volána, když je na tlačítku detekována událost kliknutí. Uvnitř voláme na naše sayHello() funkce, kterou jsme importovali nahoru. Voláním této funkce předáme tři argumenty, které jsme předpokládali při psaní funkce:name jako řetězec, objekt options s delay vlastnost a callback funkci zavolat, když je naše „práce“ hotová.

Zde chceme, aby naše funkce říkala Hello, Ryan! po třísekundovém zpoždění. Za předpokladu, že vše funguje, protože používáme console.log() pro přihlášení response na sayHello v naší funkci zpětného volání (očekáváme, že to bude naše greeting řetězec), po 3 sekundách bychom měli vidět Hello, Ryan! vytištěné na konzoli.

I když to funguje, není to ideální, protože v některých kontextech (např. když musíme čekat na několik asynchronních funkcí/funkcí založených na zpětném volání najednou), riskujeme, že vytvoříme to, co je známé jako „peklo zpětného volání“ nebo nekonečně vnořená zpětná volání čekat na dokončení každého hovoru.

Naštěstí, aby se tomu zabránilo, byly do jazyka zavedeny přísliby JavaScript a vedle nich i async/await vzor. Nyní vezmeme sayHello() funkci, zabalte ji do Promise a pak se podívejte, jak dokáže vyčistit náš kód v době hovoru.

Zabalení funkce založené na zpětném volání do Promise

Chcete-li napsat naši verzi sayHello zabalenou do slibu , budeme se spoléhat na methods funkce komponent joysticku. I když to není nutné, aby to fungovalo (funkci, kterou se chystáme napsat, můžete napsat do samostatného souboru podobného tomu, jak jsme napsali /lib/sayHello.js ), bude vše udržovat v kontextu a snáze pochopitelné.

/ui/pages/index/index.js

import ui from '@joystick.js/ui';
import sayHello from '../../../lib/sayHello';

const Index = ui.component({
  methods: {
    sayHello: (name = '', options = {}) => {
      return new Promise((resolve, reject) => {
        sayHello(name, options, (error, response) => {
          if (error) {
            reject(error);
          } else {
            resolve(response);
          }
        });
      }); 
    }
  },
  events: {
    'click button': async (event, component) => {
      const greeting = await component.methods.sayHello('Ryan', { delay: 3000 });
      console.log(greeting);
      // sayHello('Ryan', { delay: 3000 }, (error, response) => {
      //   if (error) {
      //     console.warn(error);
      //   } else {
      //     console.log(response);
      //   }
      // });
    },
  },
  render: () => {
    return `
      <div>
        <button>Do the Thing</button>
      </div>
    `;
  },
});

export default Index;

Zde jsme přidali další vlastnost k objektu options předanému našemu ui.component() funkce s názvem methods . Zde přiřazený objekt nám umožňuje definovat různé funkce, které jsou dostupné jinde v naší komponentě.

Zde jsme definovali metodu sayHello (nezaměňovat s importovaným sayHello nahoru), který obsahuje dva argumenty:name a options .

Uvnitř těla funkce máme return volání na new Promise() definovat nový příslib JavaScriptu a to , předáme funkci, která obdrží své vlastní dva argumenty:resolve a reject . Uvnitř by věci měly začít vypadat povědomě. Zde voláme na číslo sayHello , přenášející name a options předáno našemu sayHello metoda .

Myšlenka je taková, že naše metoda bude fungovat jako "proxy" nebo dálkové ovládání pro náš původní sayHello funkce. Rozdíl je v tom, že pro funkci zpětného volání si všimněte, že bereme možné error a response z sayHello a místo jejich protokolování do konzole je předáme buď reject() pokud dojde k chybě, nebo resolve() pokud dostaneme zpět úspěšnou odpověď (naše greeting řetězec).

Vraťte se do našeho click button handler, vidíme, že se to používá. Verzi sayHello založenou na zpětném volání jsme okomentovali takže vidíme rozdíl.

Před funkcí předán click button , přidali jsme async pro JavaScript, že náš kód bude používat await klíčové slovo někde uvnitř funkce předávané do click button . Když se podíváme na náš refaktor, děláme přesně to. Zde z component instance automaticky předána jako druhý argument naší funkci obsluhy události, kterou zavoláme component.methods.sayHello() předáváním name řetězec a options objekt, který chceme přenést do původního sayHello funkce.

Před něj umístíme await klíčové slovo, které JavaScriptu sdělí, aby počkal na příslib vrácený naším sayHello metoda na komponentě k vyřešení. Když se tak stane, očekáváme greeting řetězec, který má být předán resolve() který bude uložen v const greeting zde (v tomto příkladu tři sekundy po volání metody).

Nakonec, jakmile se vrátíme k výsledku, dostaneme console.log(greeting) . Pěkné na tom je, že jsme náš kód nejen zefektivnili, ale dostatečně zjednodušili, takže jej můžeme volat vedle jiných Promises, aniž bychom museli vkládat spoustu zpětných volání.

Zabalení

V tomto tutoriálu jsme se naučili, jak převzít existující asynchronní funkci založenou na zpětném volání a zabalit ji do JavaScript Promise, aby její volání spotřebovalo méně kódu a hrálo se pěkně s jiným asynchronním kódem založeným na Promise. Naučili jsme se, jak definovat původní funkci založenou na zpětném volání a použít ji, abychom diskutovali o jejích nevýhodách, a nakonec jsme se naučili, jak používat methods joysticku funkce, která nám pomůže definovat naši funkci obálky založenou na slibu.