Πώς να γράψετε τη δική σας συνάρτηση Promisify από το μηδέν

Εισαγωγή

Σε αυτό το άρθρο, θα μάθετε πώς να γράφετε τη δική σας συνάρτηση promisify από το μηδέν.

Η προωθητική ενέργεια βοηθά στην αντιμετώπιση των API που βασίζονται σε επανάκληση, διατηρώντας παράλληλα τον κώδικα με τις υποσχέσεις.

Θα μπορούσαμε απλώς να ολοκληρώσουμε οποιαδήποτε λειτουργία new Promise()και να μην ανησυχούμε καθόλου. Το να το κάνουμε αυτό όταν έχουμε πολλές λειτουργίες θα ήταν περιττό.

Εάν καταλαβαίνετε υποσχέσεις και επιστροφές κλήσεων, τότε η εκμάθηση πώς να γράφετε συναρτήσεις υπόσχεσης θα πρέπει να είναι εύκολη. Ας ξεκινήσουμε λοιπόν.

Αλλά έχετε αναρωτηθεί ποτέ πώς λειτουργεί η υπόσχεση;

Το σημαντικό είναι να μην σταματήσετε να αναρωτιέστε. Η περιέργεια έχει τον δικό της λόγο ύπαρξης.

- Albert Einstein

Οι υποσχέσεις παρουσιάστηκαν στο ECMA-262 Standard, 6th Edition (ES6) που δημοσιεύθηκε τον Ιούνιο του 2015.

Ήταν μια σημαντική βελτίωση σε σχέση με τις επιστροφές κλήσεων, καθώς όλοι γνωρίζουμε πόσο δυσανάγνωστο μπορεί να είναι το "callback hell" :)

Ως προγραμματιστής του Node.js, πρέπει να γνωρίζετε τι είναι μια υπόσχεση και πώς λειτουργεί εσωτερικά, κάτι που θα σας βοηθήσει επίσης σε συνεντεύξεις JS. Μη διστάσετε να τα ελέγξετε γρήγορα πριν διαβάσετε.

Γιατί πρέπει να μετατρέψουμε τις επιστροφές κλήσεων σε υποσχέσεις;

  1. Με τις επιστροφές κλήσεων, εάν θέλετε να κάνετε κάτι διαδοχικά, θα πρέπει να καθορίσετε ένα errόρισμα σε κάθε επανάκληση, το οποίο είναι περιττό. Στις υποσχέσεις ή στο async-wait, μπορείτε απλώς να προσθέσετε μια .catchμέθοδο ή αποκλεισμό που θα εντοπίσει τυχόν σφάλματα που προέκυψαν στην αλυσίδα υπόσχεσης
  2. Με τις επιστροφές κλήσεων, δεν έχετε κανέναν έλεγχο για το πότε καλείται, σε ποιο πλαίσιο ή πόσες φορές καλείται, που μπορεί να οδηγήσει σε διαρροές μνήμης.
  3. Χρησιμοποιώντας υποσχέσεις, ελέγχουμε αυτούς τους παράγοντες (ειδικά τον χειρισμό σφαλμάτων) έτσι ο κώδικας είναι πιο ευανάγνωστος και διατηρήσιμος.

Πώς να κάνετε τις λειτουργίες που βασίζονται στην επανάκληση να επιστρέψουν μια υπόσχεση

Υπάρχουν δύο τρόποι για να το κάνετε:

  1. Τυλίξτε τη συνάρτηση σε μια άλλη συνάρτηση που επιστρέφει μια υπόσχεση. Στη συνέχεια επιλύει ή απορρίπτει με βάση τα ορίσματα επανάκλησης.
  2. Promisification - Δημιουργούμε μια λειτουργία util / helper η promisifyοποία θα μετατρέψει όλα τα API που βασίζονται σε επαναφορά μέσω σφάλματος.

Παράδειγμα: υπάρχει ένα API που βασίζεται σε επανάκληση που παρέχει το άθροισμα των δύο αριθμών. Θέλουμε να το υποσχεθούμε ώστε να επιστρέψει μια thenableυπόσχεση.

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

Τυλίξτε μια υπόσχεση

Όπως μπορείτε να δείτε, getSumPromiseμεταβιβάζει όλη την εργασία στην αρχική λειτουργία getSumAsync, παρέχοντας τη δική του επιστροφή που μεταφράζεται σε υπόσχεση resolve/reject.

Υποσχεθείτε

Όταν πρέπει να υποσχεθούμε πολλές λειτουργίες, μπορούμε να δημιουργήσουμε μια βοηθητική συνάρτηση promisify.

Τι είναι η υπόσχεση;

Η υπόσχεση σημαίνει μεταμόρφωση. Είναι μια μετατροπή μιας συνάρτησης που δέχεται μια επανάκληση σε μια συνάρτηση που επιστρέφει μια υπόσχεση.

Χρήση του 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); })

Άρα μοιάζει με μια μαγική λειτουργία που μεταμορφώνεται getSumAsyncσε αυτήν getSumPromiseπου έχει .thenκαι .catchμεθόδους

Ας γράψουμε τη δική μας συνάρτηση promisify:

Εάν κοιτάξετε το βήμα 1 στον παραπάνω κώδικα, η promisifyσυνάρτηση δέχεται μια συνάρτηση ως όρισμα, οπότε το πρώτο πράγμα που πρέπει να κάνουμε είναι να γράψουμε μια συνάρτηση που μπορεί να κάνει το ίδιο:

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

Μετά από αυτό, getSumPromise(1, 1)είναι μια κλήση λειτουργίας. Αυτό σημαίνει ότι το promisify θα πρέπει να επιστρέψει μια άλλη συνάρτηση που μπορεί να κληθεί με τα ίδια ορίσματα της αρχικής συνάρτησης:

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

Στον παραπάνω κώδικα μπορείτε να δείτε ότι διαδίδουμε επιχειρήματα επειδή δεν γνωρίζουμε πόσα ορίσματα έχει η αρχική συνάρτηση. argsθα είναι ένας πίνακας που περιέχει όλα τα ορίσματα.

Όταν καλείτε getSumPromise(1, 1), πραγματικά καλείτε (...args)=> {}. Στην παραπάνω εφαρμογή επιστρέφει μια υπόσχεση. Γι 'αυτό μπορείτε να χρησιμοποιήσετε getSumPromise(1, 1).then(..).catch(..).

Ελπίζω να έχετε υποδείξει ότι η λειτουργία περιτύλιξης (...args) => {}πρέπει να επιστρέψει μια υπόσχεση.

Επιστρέψτε μια υπόσχεση

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

Τώρα το δύσκολο μέρος είναι πώς να αποφασίσετε πότε σε resolve or rejectμια υπόσχεση.

Actually, that will be decided by the original getSumAsync function implementation – it will call the original callback function and we just need to define it. Then based on err and result we will reject or  resolve the promise.

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

Our args[] only consists of arguments passed by getSumPromise(1, 1) except the callback function. So you need to add customCallback(err, result) to the args[]which the original function getSumAsync will call accordingly as we are tracking the result in customCallback.

Push customCallback to 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) }) } }

As you can see, we have added fn.call(this, args), which will call the original function under the same context with the arguments getSumAsync(1, 1, customCallback). Then our promisify function should be able to resolve/reject accordingly.

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

Μπορείτε να διαβάσετε άλλα άρθρα όπως αυτό στο 101node.io.