Kako ispisati vlastitu funkciju Promisify iz početka

Uvod

U ovom ćete članku naučiti kako ispisati vlastitu funkciju promisify ispočetka.

Promisifikacija pomaže u rješavanju API-ja temeljenih na povratnom pozivu, a istovremeno održava usklađenost koda s obećanjima.

Mogli bismo jednostavno omotati bilo koju funkciju new Promise()i uopće se ne brinuti zbog nje. Ali raditi to kad imamo mnogo funkcija bilo bi suvišno.

Ako razumijete obećanja i povratne pozive, učenje pisanja funkcija promisify trebalo bi biti jednostavno. Pa krenimo.

No, jeste li se ikad zapitali kako promisify djeluje?

Važno je ne prestati ispitivati. Znatiželja ima svoj razlog postojanja.

- Albert Einstein

Obećanja su uvedena u ECMA-262 Standard, 6. izdanje (ES6) koje je objavljeno u lipnju 2015.

To je bilo poprilično poboljšanje u odnosu na povratne pozive, jer svi znamo koliko "vrag za povratni poziv" može biti nečitljiv :)

Kao programer Node.js trebali biste znati što je obećanje i kako interno funkcionira, što će vam također pomoći u JS intervjuima. Slobodno ih brzo pregledajte prije čitanja.

Zašto povratne pozive moramo pretvoriti u obećanja?

  1. S povratnim pozivima, ako želite nešto raditi uzastopno, morat ćete navesti errargument u svakom povratnom pozivu, koji je suvišan. U obećanja ili async-await možete samo dodati .catchmetodu ili blok koji će uhvatiti sve pogreške koje su se dogodile u lancu obećanja
  2. Uz povratne pozive nemate kontrolu nad time kada se poziva, u kojem kontekstu ili koliko puta se zove, što može dovesti do curenja memorije.
  3. Koristeći obećanja, kontroliramo ove čimbenike (posebno rukovanje pogreškama) tako da je kôd čitljiviji i održiviji.

Kako natjerati funkcije temeljene na povratnom pozivu da vraćaju obećanje

Postoje dva načina za to:

  1. Zamotajte funkciju u drugu funkciju koja vraća obećanje. Zatim rješava ili odbija na temelju argumenata povratnog poziva.
  2. Promisifikacija - Stvaramo uslužnu / pomoćnu funkciju promisifykoja će transformirati sve API-je koji se temelje na povratnim pozivima.

Primjer: postoji API zasnovan na povratnom pozivu koji pruža zbroj dva broja. Želimo ga obećati tako da nam vrati thenableobećanje.

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 } })

Umotajte u obećanje

Kao što vidite, getSumPromisesav posao delegira izvornoj funkciji getSumAsync, pružajući vlastiti povratni poziv koji u prijevodu znači obećavanje resolve/reject.

Promisificirati

Kad trebamo obećavati mnoge funkcije, možemo stvoriti pomoćnu funkciju promisify.

Što je Promisifikacija?

Promisifikacija znači preobrazbu. To je pretvorba funkcije koja prihvaća povratni poziv u funkciju koja vraća obećanje.

Korištenje Node.js-a 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); })

Tako izgleda kao čarobna funkcija koja se pretvara getSumAsyncu getSumPromisekoju ima .theni .catchmetode

Napišimo vlastitu funkciju promisify:

Ako pogledate 1. korak u gornjem kodu, promisifyfunkcija prihvaća funkciju kao argument, pa prvo što moramo učiniti je funkcija koja može učiniti isto:

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

Nakon toga getSumPromise(1, 1)slijedi poziv funkcije. To znači da bi naš promisify trebao vratiti drugu funkciju koja se može pozvati s istim argumentima izvorne funkcije:

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

U gornjem kodu možete vidjeti da širimo argumente jer ne znamo koliko argumenata ima izvorna funkcija. argsbit će niz koji sadrži sve argumente.

Kad zovete, getSumPromise(1, 1)zapravo zovete (...args)=> {}. U gornjoj implementaciji vraća obećanje. Zbog toga ste u mogućnosti koristiti getSumPromise(1, 1).then(..).catch(..).

Nadam se da ste dobili naslutiti da bi funkcija omota (...args) => {}trebala vratiti obećanje.

Vrati obećanje

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

Sada je lukav dio kako odlučiti kada resolve or rejectćete obećati.

Zapravo, o tome će odlučiti izvorna getSumAsyncimplementacija funkcije - ona će pozvati izvornu funkciju povratnog poziva i mi je samo trebamo definirati. Zatim na temelju erri resulthoćemo rejectili   resolveobećanja.

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

Naši se args[]sastoje samo od argumenata koje je proslijedio, getSumPromise(1, 1)osim funkcije povratnog poziva. Dakle, morate dodati customCallback(err, result)na args[]kojem je izvorna funkcija getSumAsyncće pozvati u skladu s tim kako smo praćenje rezultat u customCallback.

Pritisnite customCallback u argumente []

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) }) } }

Kao što vidite, dodali smo fn.call(this, args), što će pozvati izvornu funkciju u istom kontekstu s argumentima getSumAsync(1, 1, customCallback). Tada bi naša funkcija promisify trebala moći biti u resolve/rejectskladu s tim.

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

You can read other articles like this at 101node.io.