Πώς να κάνετε μια υπόσχεση από μια λειτουργία επιστροφής κλήσης σε JavaScript

Οι προγραμματιστές υποστήριξης αντιμετωπίζουν συνεχώς προκλήσεις ενώ δημιουργούν εφαρμογές ή δοκιμάζουν κώδικα. Ως προγραμματιστής που είναι αρκετά νέος και εξοικειώνεται με αυτές τις προκλήσεις, δεν έχω αντιμετωπίσει ποτέ μια πρόκληση ή ταλαιπωρία πιο συχνά - ή πιο αξέχαστη - από ό, τι με τις λειτουργίες επανάκλησης.

Δεν πρόκειται να ερευνήσω πολύ βαθιά τις λεπτομέρειες της επανάκλησης και των πλεονεκτημάτων και των μειονεκτημάτων της ή εναλλακτικές λύσεις, όπως υποσχέσεις και ασύμμετρη / αναμονή. Για μια πιο ζωντανή εξήγηση, μπορείτε να δείτε αυτό το άρθρο που τους εξηγεί διεξοδικά.

Callback Hell

Οι επιστροφές κλήσης είναι ένα χρήσιμο χαρακτηριστικό των JavaScript που του επιτρέπει να πραγματοποιεί ασύγχρονες κλήσεις. Πρόκειται για συναρτήσεις που συνήθως μεταβιβάζονται ως δεύτερη παράμετρος σε μια άλλη συνάρτηση που λαμβάνει δεδομένα ή κάνει μια λειτουργία I / O που απαιτεί χρόνο για να ολοκληρωθεί.

Για παράδειγμα, δοκιμάστε να πραγματοποιήσετε μια κλήση API χρησιμοποιώντας το requestμονάδα ή σύνδεση σε μια βάση δεδομένων MongoDB. Τι γίνεται όμως αν και οι δύο κλήσεις εξαρτώνται μεταξύ τους; Τι γίνεται αν τα δεδομένα που ανακτάτε είναι η διεύθυνση URL MongoDB με την οποία πρέπει να συνδεθείτε;

Θα πρέπει να τοποθετήσετε αυτές τις κλήσεις μεταξύ τους:

request.get(url, function(error, response, mongoUrl) { if(error) throw new Error("Error while fetching fetching data"); MongoClient.connect(mongoUrl, function(error, client) { if(error) throw new Error("MongoDB connection error"); console.log("Connected successfully to server"); const db = client.db("dbName"); // Do some application logic client.close(); }); });

Εντάξει… λοιπόν πού είναι το πρόβλημα; Λοιπόν, για ένα πράγμα, η αναγνωσιμότητα του κώδικα πάσχει από αυτήν την τεχνική.

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

Θα καταλήξετε σε πολλές αγκύλες κλεισίματος και σγουρά τιράντες που θα μπερδέψουν εσάς και άλλους προγραμματιστές, ανεξάρτητα από το πόσο καλά μορφοποιηθεί ο κώδικάς σας. Υπάρχει ένας ιστότοπος που ονομάζεται callbackhell που αντιμετωπίζει αυτό το συγκεκριμένο ζήτημα.

Ακούω μερικούς από εσάς, συμπεριλαμβανομένου του αφελούς παρελθόντος εαυτού μου, να μου λέει ότι το τυλίγω σε asyncσυνάρτηση τότε await η συνάρτηση επανάκλησης. Αυτό απλά δεν λειτουργεί.

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

Εδώ είναι αυτό το λάθος που έκανα πριν:

var request = require('request'); // WRONG async function(){ let joke; let url = "//api.chucknorris.io/jokes/random" await request.get(url, function(error, response, data) { if(error) throw new Error("Error while fetching fetching data"); let content = JSON.parse(data); joke = content.value; }); console.log(joke); // undefined }; // Wrong async function(){ let joke; let url = "//api.chucknorris.io/jokes/random" request.get(url, await function(error, response, data) { if(error) throw new Error("Error while fetching fetching data"); let content = JSON.parse(data); joke = content.value; }); console.log(joke); // undefined };

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

Εξάλλου, αυτό είναι απλώς ένα παράδειγμα. Μερικές φορές μπορείτε να κλειδώσετε χρησιμοποιώντας μια βιβλιοθήκη που δεν υποστηρίζει υποσχέσεις χωρίς εναλλακτικές λύσεις. Όπως η χρήση κιτ ανάπτυξης λογισμικού (SDK) για επικοινωνία με πλατφόρμες όπως το Amazon Web Services (AWS), το Twitter ή το Facebook.

Μερικές φορές, ακόμη και η χρήση μιας επιστροφής κλήσης για να κάνετε μια πολύ απλή κλήση με γρήγορη λειτουργία I / O ή CRUD είναι μια χαρά και καμία άλλη λογική δεν εξαρτάται από τα αποτελέσματά της. Ωστόσο, ενδέχεται να περιορίζεστε από το περιβάλλον χρόνου εκτέλεσης όπως σε μια λειτουργία Lambda που θα σκότωνε όλη τη διαδικασία μόλις ολοκληρωθεί το κύριο νήμα, ανεξάρτητα από τυχόν ασύγχρονες κλήσεις που δεν ολοκληρώθηκαν.

Λύση 1 (εύκολη): Χρησιμοποιήστε τη μονάδα "util" του κόμβου

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

Όπως επεσήμαναν οι Erop και Robin στα σχόλια, η έκδοση 8 και άνω του Nodejs υποστηρίζει πλέον τη μετατροπή των λειτουργιών επανάκλησης σε υποσχέσεις χρησιμοποιώντας την ενσωματωμένη μονάδα util .

const request = require('request'); const util = require('util'); const url = "//api.chucknorris.io/jokes/random"; // Use the util to promisify the request method const getChuckNorrisFact = util.promisify(request); // Use the new method to call the API in a modern then/catch pattern getChuckNorrisFact(url).then(data => { let content = JSON.parse(data.body); console.log('Joke: ', content.value); }).catch(err => console.log('error: ', err))

Ο παραπάνω κώδικας λύνει το πρόβλημα με τη χρήση του util.promisifyδιαθέσιμη μέθοδος από τη βασική βιβλιοθήκη nodejs.

Το μόνο που έχετε να κάνετε είναι να χρησιμοποιήσετε τη συνάρτηση επανάκλησης ως επιχείρημα για να χρησιμοποιήσετε .promisify και να την αποθηκεύσετε ως μεταβλητή. Στην περίπτωσή μου, αυτό είναι getChuckNorrisFact .

Στη συνέχεια, χρησιμοποιείτε αυτήν τη μεταβλητή ως συνάρτηση που μπορείτε να χρησιμοποιήσετε σαν μια υπόσχεση με τις μεθόδους .then () και .catch () .

Λύση 2 (περιλαμβάνεται): Μετατρέψτε την επιστροφή κλήσης σε υπόσχεση

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

Ας πάρουμε το παραπάνω παράδειγμα του Chuck Norris και να το μετατρέψουμε σε υπόσχεση.

var request = require('request'); let url = "//api.chucknorris.io/jokes/random"; // A function that returns a promise to resolve into the data //fetched from the API or an error let getChuckNorrisFact = (url) => { return new Promise( (resolve, reject) => { request.get(url, function(error, response, data){ if (error) reject(error); let content = JSON.parse(data); let fact = content.value; resolve(fact); }) } ); }; getChuckNorrisFact(url).then( fact => console.log(fact) // actually outputs a string ).catch( error => console.(error) );

Στον παραπάνω κώδικα, έβαλα τη requestσυνάρτηση με βάση την επιστροφή κλήσης μέσα σε ένα περιτύλιγμα Promise Promise( (resolve, reject) => { //callback function}). Αυτό το περιτύλιγμα μας επιτρέπει να καλέσουμε τη getChuckNorrisFactλειτουργία σαν υπόσχεση με τις μεθόδους .then()και .catch(). Όταν getChuckNorrisFactκαλείται, εκτελεί το αίτημα στο API και περιμένει να εκτελεστεί είτε ένα resolve()ή μια reject()δήλωση. Στη λειτουργία επανάκλησης, απλώς μεταβιβάζετε τα ανακτημένα δεδομένα στις μεθόδους επίλυσης ή απόρριψης.

Μόλις τα δεδομένα (σε αυτήν την περίπτωση, ένα φοβερό γεγονός Chuck Norris) ανακτώνται και διαβιβάζονται στον επιλυτή, getChuckNorrisFactεκτελεί τη then()μέθοδο. Αυτό θα επιστρέψει το αποτέλεσμα που μπορείτε να χρησιμοποιήσετε μέσα σε μια συνάρτηση μέσα στοthen() για να κάνετε την επιθυμητή λογική σας - σε αυτήν την περίπτωση να την εμφανίσετε στην κονσόλα.

Μπορείτε να διαβάσετε περισσότερα γι 'αυτό στα Έγγραφα Web MDN.