Sådan skriver du din egen lovende funktion fra bunden

Introduktion

I denne artikel lærer du, hvordan du skriver din egen promisify-funktion fra bunden.

Promisificering hjælper med at håndtere tilbagekaldsbaserede API'er, samtidig med at koden er i overensstemmelse med løfterne.

Vi kunne bare pakke enhver funktion med new Promise()og slet ikke bekymre os om det. Men at gøre det, når vi har mange funktioner, ville være overflødigt.

Hvis du forstår løfter og tilbagekald, bør det være let at lære at skrive promisify-funktioner. Så lad os komme i gang.

Men har du nogensinde spekuleret på, hvordan promisify fungerer?

Det vigtige er ikke at stoppe spørgsmålstegn. Nysgerrighed har sin egen grund til eksistens.

- Albert Einstein

Løfter blev introduceret i ECMA-262 Standard, 6. udgave (ES6), der blev offentliggjort i juni 2015.

Det var en ganske forbedring i forhold til tilbagekald, da vi alle ved, hvor ulæseligt "callback helvede" kan være :)

Som Node.js-udvikler skal du vide, hvad et løfte er, og hvordan det fungerer internt, hvilket også vil hjælpe dig i JS-interviews. Du er velkommen til at gennemgå dem hurtigt, før du læser videre.

Hvorfor har vi brug for at konvertere tilbagekald til løfter?

  1. Hvis du vil gøre noget i rækkefølge med tilbagekald, skal du angive et errargument i hver tilbagekaldelse, som er overflødig. I løfter eller asynk-afvent, kan du bare tilføje en .catchmetode eller blokering, der fanger eventuelle fejl, der opstod i løftekæden
  2. Med tilbagekald har du ingen kontrol over, hvornår det kaldes, under hvilken sammenhæng eller hvor mange gange det kaldes, hvilket kan føre til hukommelseslækage.
  3. Ved hjælp af løfter styrer vi disse faktorer (især fejlhåndtering), så koden er mere læsbar og vedligeholdelig.

Sådan får du tilbagekaldsbaserede funktioner til at give et løfte

Der er to måder at gøre det på:

  1. Indpak funktionen i en anden funktion, der returnerer et løfte. Det løser eller afviser derefter baseret på tilbagekaldsargumenter.
  2. Promisifikation - Vi opretter en util / hjælper-funktion, promisifyder vil transformere alle fejl først tilbagekaldsbaserede API'er.

Eksempel: der er en tilbagekaldsbaseret API, der giver summen af ​​to tal. Vi vil love det, så det giver et thenableløfte.

const getSumAsync = (num1, num2, callback) => { if (!num1 || !num2) { return callback(new Error("Missing arguments"), null); } return callback(null, num1 + num2); } getSumAsync(1, 1, (err, result) => { if (err){ doSomethingWithError(err) }else { console.log(result) // 2 } })

Sæt dig ind i et løfte

Som du kan se, getSumPromisedelegerer alt arbejdet til den oprindelige funktion getSumAsyncog giver sit eget tilbagekald, der oversættes til løfte resolve/reject.

Lovgiv

Når vi har brug for at lovgive mange funktioner, kan vi oprette en hjælperfunktion promisify.

Hvad er lovgivning?

Promisificering betyder transformation. Det er en konvertering af en funktion, der accepterer et tilbagekald til en funktion, der returnerer et løfte.

Brug af Node.js's util.promisify():

const { promisify } = require('util') const getSumPromise = promisify(getSumAsync) // step 1 getSumPromise(1, 1) // step 2 .then(result => { console.log(result) }) .catch(err =>{ doSomethingWithError(err); })

Så det ligner en magisk funktion, der omdannes getSumAsynctil getSumPromisehvilken har .thenog .catchmetoder

Lad os skrive vores egen promisify-funktion:

Hvis du ser på trin 1 i ovenstående kode, promisifyaccepterer funktionen en funktion som et argument, så den første ting, vi skal gøre, skriv en funktion, der kan gøre det samme:

const getSumPromise = myPromisify(getSumAsync) const myPromisify = (fn) => {}

Derefter getSumPromise(1, 1)er der et funktionsopkald. Dette betyder, at vores promisify skal returnere en anden funktion, der kan kaldes med de samme argumenter som den oprindelige funktion:

const myPromisify = (fn) => { return (...args) => { } }

I ovenstående kode kan du se, at vi spreder argumenter, fordi vi ikke ved, hvor mange argumenter den oprindelige funktion har. argsvil være en matrix, der indeholder alle argumenterne.

Når du ringer, ringer getSumPromise(1, 1)du faktisk (...args)=> {}. I implementeringen ovenfor returnerer det et løfte. Derfor er du i stand til at bruge getSumPromise(1, 1).then(..).catch(..).

Jeg håber, du har fået antydningen om, at indpakningsfunktionen (...args) => {}skal give et løfte.

Returner et løfte

const myPromisify = (fn) => { return (...args) => { return new Promise((resolve, reject) => { }) } }

Nu er den vanskelige del, hvordan man beslutter, hvornår man skal resolve or rejectløfte.

Faktisk vil det blive bestemt af den oprindelige getSumAsyncfunktionsimplementering - den kalder den oprindelige tilbagekaldsfunktion, og vi skal bare definere den. Derefter baseret på errog resultvi vil rejecteller   resolveløftet.

const myPromisify = (fn) => { return (...args) => { return new Promise((resolve, reject) => { function customCallback(err, result) { if (err) { reject(err) }else { resolve(result); } } }) } }

Vores args[]består kun af argumenter, der er sendt forbi getSumPromise(1, 1)undtagen tilbagekaldsfunktionen. Så du skal tilføje customCallback(err, result)til den, args[]som den oprindelige funktion getSumAsynckalder i overensstemmelse hermed, da vi sporer resultatet i customCallback.

Skub customCallback til args []

const myPromisify = (fn) => { return (...args) => { return new Promise((resolve, reject) => { function customCallback(err, result) { if (err) { reject(err) }else { resolve(result); } } args.push(customCallback) fn.call(this, ...args) }) } }

Som du kan se, har vi tilføjet fn.call(this, args), som kalder den oprindelige funktion under samme sammenhæng med argumenterne getSumAsync(1, 1, customCallback). Så skulle vores promisify-funktion være i stand til i resolve/rejectoverensstemmelse hermed.

The above implementation will work when the original function expects a callback with two arguments, (err, result). That’s what we encounter most often. Then our custom callback is in exactly the right format and promisify works great for such a case.

But what if the original fn expects a callback with more arguments likecallback(err, result1, result2, ...)?

In order to make it compatible with that, we need to modify our myPromisify function which will be an advanced version.

const myPromisify = (fn) => { return (...args) => { return new Promise((resolve, reject) => { function customCallback(err, ...results) { if (err) { return reject(err) } return resolve(results.length === 1 ? results[0] : results) } args.push(customCallback) fn.call(this, ...args) }) } }

Example:

const getSumAsync = (num1, num2, callback) => { if (!num1 || !num2) { return callback(new Error("Missing dependencies"), null); } const sum = num1 + num2; const message = `Sum is ${sum}` return callback(null, sum, message); } const getSumPromise = myPromisify(getSumAsync) getSumPromise(2, 3).then(arrayOfResults) // [6, 'Sum is 6']

That’s all! Thank you for making it this far!

I hope you’re able to grasp the concept. Try to re-read it again. It’s a bit of code to wrap your head around, but not too complex. Let me know if it was helpful ?

Don’t forget to share it with your friends who are starting with Node.js or need to level up their Node.js skills.

References:

//nodejs.org/dist/latest-v8.x/docs/api/util.html#util_util_promisify_original

//github.com/digitaldesignlabs/es6-promisify

Du kan læse andre artikler som denne på 101node.io.