So schreiben Sie Ihre eigene Promisify-Funktion von Grund auf neu

Einführung

In diesem Artikel erfahren Sie, wie Sie Ihre eigene Promisify-Funktion von Grund auf neu schreiben.

Promisification hilft beim Umgang mit Callback-basierten APIs, während der Code mit Versprechungen konsistent bleibt.

Wir könnten einfach jede Funktion damit new Promise()abschließen und uns überhaupt keine Sorgen machen. Aber das zu tun, wenn wir viele Funktionen haben, wäre überflüssig.

Wenn Sie Versprechen und Rückrufe verstehen, sollte es einfach sein, das Schreiben von Versprechenfunktionen zu lernen. Also lasst uns anfangen.

Aber haben Sie sich jemals gefragt, wie Promisify funktioniert?

Das Wichtigste ist, nicht aufzuhören zu fragen. Neugier hat seinen eigenen Existenzgrund.

- Albert Einstein

Versprechen wurden in den ECMA-262 Standard, 6. Ausgabe (ES6) aufgenommen, der im Juni 2015 veröffentlicht wurde.

Es war eine ziemliche Verbesserung gegenüber Rückrufen, da wir alle wissen, wie unlesbar "Rückrufhölle" sein kann :)

Als Node.js-Entwickler sollten Sie wissen, was ein Versprechen ist und wie es intern funktioniert. Dies hilft Ihnen auch bei JS-Interviews. Fühlen Sie sich frei, sie schnell zu überprüfen, bevor Sie weiterlesen.

Warum müssen wir Rückrufe in Versprechen umwandeln?

  1. Wenn Sie bei Rückrufen etwas nacheinander ausführen möchten, müssen Sie errin jedem Rückruf ein Argument angeben , das redundant ist. Bei Versprechungen oder asynchronem Warten können Sie einfach eine .catchMethode oder einen Block hinzufügen , die alle Fehler in der Versprechenskette abfangen
  2. Bei Rückrufen haben Sie keine Kontrolle darüber, wann, in welchem ​​Kontext oder wie oft es aufgerufen wird, was zu Speicherverlusten führen kann.
  3. Mithilfe von Versprechungen steuern wir diese Faktoren (insbesondere die Fehlerbehandlung), damit der Code besser lesbar und wartbar ist.

Wie man Callback-basierte Funktionen zum Versprechen macht

Es gibt zwei Möglichkeiten, dies zu tun:

  1. Wickeln Sie die Funktion in eine andere Funktion ein, die ein Versprechen zurückgibt. Es wird dann basierend auf Rückrufargumenten aufgelöst oder abgelehnt.
  2. Promisification - Wir erstellen eine util / helper-Funktion, promisifydie alle auf Fehler-First-Callback basierenden APIs transformiert.

Beispiel: Es gibt eine Callback-basierte API, die die Summe von zwei Zahlen liefert. Wir wollen es versprechen, damit es ein thenableVersprechen zurückgibt .

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

In ein Versprechen einwickeln

Wie Sie sehen können, getSumPromisedelegiert die gesamte Arbeit an die ursprüngliche Funktion getSumAsyncund stellt einen eigenen Rückruf bereit, der sich in Versprechen umsetzt resolve/reject.

Versprich es mir

Wenn wir viele Funktionen versprechen müssen, können wir eine Hilfsfunktion erstellen promisify.

Was ist Versprechen?

Versprechen bedeutet Transformation. Es ist eine Umwandlung einer Funktion, die einen Rückruf akzeptiert, in eine Funktion, die ein Versprechen zurückgibt.

Verwenden von Node.js 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); })

Es sieht also aus wie eine magische Funktion, die sich getSumAsyncin getSumPromisewelche hat .thenund .catchMethoden verwandelt

Schreiben wir unsere eigene Promisify-Funktion:

Wenn Sie sich Schritt 1 im obigen Code ansehen , promisifyakzeptiert die Funktion eine Funktion als Argument. Als erstes müssen wir also eine Funktion schreiben, die dasselbe tun kann:

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

Danach getSumPromise(1, 1)erfolgt ein Funktionsaufruf. Dies bedeutet, dass unser Versprechen eine andere Funktion zurückgeben sollte, die mit denselben Argumenten wie die ursprüngliche Funktion aufgerufen werden kann:

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

Im obigen Code sehen Sie, dass wir Argumente verbreiten, weil wir nicht wissen, wie viele Argumente die ursprüngliche Funktion hat. argswird ein Array sein, das alle Argumente enthält.

Wenn Sie anrufen, rufen getSumPromise(1, 1)Sie tatsächlich an (...args)=> {}. In der obigen Implementierung wird ein Versprechen zurückgegeben. Deshalb können Sie verwenden getSumPromise(1, 1).then(..).catch(..).

Ich hoffe, Sie haben den Hinweis erhalten, dass die Wrapper-Funktion (...args) => {}ein Versprechen zurückgeben sollte.

Gib ein Versprechen zurück

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

Jetzt ist der schwierige Teil, wie man entscheidet, wann man resolve or rejectein Versprechen abgibt.

Tatsächlich wird dies von der ursprünglichen getSumAsyncFunktionsimplementierung entschieden - es wird die ursprüngliche Rückruffunktion aufgerufen und wir müssen sie nur definieren. Dann basierend auf errund resultwir werden rejectoder   resolvedas Versprechen.

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

Unser args[]einziges besteht aus Argumenten, die mit getSumPromise(1, 1)Ausnahme der Rückruffunktion übergeben werden. Sie müssen also das hinzufügen customCallback(err, result), args[]was die ursprüngliche Funktion entsprechend getSumAsyncaufruft, wenn wir das Ergebnis in verfolgen customCallback.

Schieben Sie customCallback auf 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) }) } }

Wie Sie sehen können, haben wir hinzugefügt fn.call(this, args), dass die ursprüngliche Funktion im selben Kontext wie die Argumente aufgerufen wird getSumAsync(1, 1, customCallback). Dann sollte unsere Promisify-Funktion dazu in der Lage resolve/rejectsein.

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.