Το αντικείμενο Promise αντιπροσωπεύει την τελική ολοκλήρωση (ή αποτυχία) μιας ασύγχρονης λειτουργίας και την τιμή που προκύπτει από αυτήν.
Αυτό είναι ένα δύσκολο θέμα για πολλούς ανθρώπους, ειδικά αν ξέρεις προγραμματισμό σε μια γλώσσα που είναι εντελώς σύγχρονη. Αν νιώθεις ότι σε ξεπερνάει, ή αν θα ήθελες να μάθεις περισσότερα για την ταυτοχρονικότητα και τον παραλληλισμό, δες (μέσω go.dev) ή δες απευθείας μέσω vimeo και διάβασε τις διαφάνειες της υπέροχης ομιλίας "Concurrency is not parallelism".
Μια Promise έχει τρεις καταστάσεις:
Όταν δημιουργείται, μια υπόσχεση είναι εκκρεμής. Κάποια στιγμή στο μέλλον μπορεί να επιλυθεί ή να απορριφθεί. Μόλις μια υπόσχεση επιλυθεί ή απορριφθεί μία φορά, δεν μπορεί ποτέ ξανά να επιλυθεί ή να απορριφθεί, ούτε μπορεί να αλλάξει η κατάστασή της.
Με άλλα λόγια:
Μια υπόσχεση μπορεί να επιλυθεί με διάφορους τρόπους:
// Creates a promise that is immediately resolved
Promise.resolve(value);
// Creates a promise that is immediately resolved
new Promise((resolve) => {
resolve(value);
});
// Chaining a promise leads to a resolved promise
somePromise.then(() => {
// ...
return value;
});
Στα παραπάνω παραδείγματα το value μπορεί να είναι οτιδήποτε, συμπεριλαμβανομένου ενός σφάλματος, του undefined, του null ή μιας άλλης υπόσχεσης.
Συνήθως θέλεις να επιλύσεις με μια τιμή που δεν είναι σφάλμα.
Μια υπόσχεση μπορεί να απορριφθεί με διάφορους τρόπους:
// Creates a promise that is immediately rejected
Promise.reject(reason)
// Creates a promise that is immediately rejected
new Promise((_, reject) {
reject(reason)
})
// Chaining a promise with an error leads to a rejected promise
somePromise.then(() => {
// ...
throw reason
})
Στα παραπάνω παραδείγματα το reason μπορεί να είναι οτιδήποτε, συμπεριλαμβανομένου ενός σφάλματος, του undefined ή του null.
Συνήθως θέλεις να απορρίψεις με ένα σφάλμα.
Μια υπόσχεση μπορεί να συνεχιστεί με μια μελλοντική ενέργεια μόλις επιλυθεί ή απορριφθεί.
promise.then() καλείται μόλις επιλυθεί η promise
promise.catch() καλείται μόλις απορριφθεί η promise
promise.finally() καλείται μόλις η promise είτε επιλυθεί είτε απορριφθείΚάθε υπόσχεση είναι "thenable".
Αυτό σημαίνει ότι υπάρχει μια διαθέσιμη συνάρτηση then που θα εκτελεστεί μόλις επιλυθεί η αρχική υπόσχεση.
Δεδομένου του promise.then(onResolved), το callback onResolved λαμβάνει την τιμή με την οποία επιλύθηκε η αρχική υπόσχεση.
Αυτό θα επιστρέφει πάντα μια νέα "αλυσιδωτή" υπόσχεση.
Η επιστροφή ενός value από το then επιλύει την "αλυσιδωτή" υπόσχεση.
Η ρίψη ενός reason στο then απορρίπτει την "αλυσιδωτή" υπόσχεση.
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
});
const promise2 = promise1.then(function (value) {
console.log(value);
// expected output: "Success!"
return true;
});
Αυτό θα τυπώσει το "Success!" μετά από περίπου 1000 ms.
Η κατάσταση και η τιμή του promise1 θα είναι resolved και "Success!".
Η κατάσταση και η τιμή του promise2 θα είναι resolved και true.
Υπάρχει ένα δεύτερο διαθέσιμο όρισμα που εκτελείται όταν απορριφθεί η αρχική υπόσχεση.
Δεδομένου του promise.then(onResolved, onRejected), το callback onResolved λαμβάνει την τιμή με την οποία επιλύθηκε η αρχική υπόσχεση, ή το callback onRejected λαμβάνει τον λόγο για τον οποίο απορρίφθηκε η υπόσχεση.
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
if (Math.random() < 0.5) {
reject('Nope!');
}
});
function log(value) {
console.log(value);
return true;
}
function shout(reason) {
console.error(reason.toUpperCase());
return false;
}
const promise2 = promise1.then(log, shout);
"Success!" μετά από περίπου 1000 ms.
promise1 θα είναι resolved και "Success!".promise2 θα είναι resolved και true."NOPE!".
promise1 θα είναι rejected και Nope!.promise2 θα είναι resolved και false.Είναι σημαντικό να καταλάβεις ότι, λόγω των κανόνων του κύκλου ζωής, όταν κάνει reject, το resolve που έρχεται ~1000ms αργότερα αγνοείται σιωπηλά, καθώς η εσωτερική κατάσταση δεν μπορεί να αλλάξει μόλις απορριφθεί ή επιλυθεί.
Είναι σημαντικό να καταλάβεις ότι η επιστροφή μιας τιμής από μια υπόσχεση την επιλύει, και η ρίψη μιας τιμής την απορρίπτει.
Όταν το promise1 επιλυθεί και υπάρχει συνδεδεμένο onResolved: then(onResolved), τότε αυτή η συνέχεια είναι μια νέα υπόσχεση που μπορεί να επιλυθεί ή να απορριφθεί.
Όταν το promise1 απορριφθεί αλλά υπάρχει συνδεδεμένο onRejected: then(, onRejected), τότε αυτή η συνέχεια είναι μια νέα υπόσχεση που μπορεί να επιλυθεί ή να απορριφθεί.
Μερικές φορές θέλεις να συλλάβεις σφάλματα και να συνεχίσεις μόνο όταν η αρχική υπόσχεση κάνει reject.
Δεδομένου του promise.catch(onCatch), το callback onCatch λαμβάνει τον λόγο για τον οποίο απορρίφθηκε η αρχική υπόσχεση.
Αυτό θα επιστρέφει πάντα μια νέα "αλυσιδωτή" υπόσχεση.
Η επιστροφή ενός value από το catch επιλύει την "αλυσιδωτή" υπόσχεση.
Η ρίψη ενός reason στο catch απορρίπτει την "αλυσιδωτή" υπόσχεση.
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
if (Math.random() < 0.5) {
reject('Nope!');
}
});
function log(value) {
console.log(value);
return 'done';
}
function recover(reason) {
console.error(reason.toUpperCase());
return 42;
}
const promise2 = promise1.catch(recover).then(log);
Σε περίπου 1/2 των περιπτώσεων, αυτό θα τυπώσει το "Success!" μετά από περίπου 1000 ms.
Στην άλλη 1/2 των περιπτώσεων, αυτό θα τυπώσει αμέσως το 42.
promise1 επιλυθεί, το catch παραλείπεται και φτάνει στο then, και τυπώνει την τιμή.
promise1 θα είναι resolved και "Success!".promise2 θα είναι resolved και "done";promise1 απορριφθεί, το catch εκτελείται, το οποίο επιστρέφει μια τιμή, και έτσι η αλυσίδα είναι τώρα resolved, και φτάνει στο then, και τυπώνει την τιμή.
promise1 θα είναι rejected και "Nope!".promise2 θα είναι resolved και "done";Μερικές φορές θέλεις να εκτελέσεις κώδικα αφού μια υπόσχεση ολοκληρωθεί, ανεξάρτητα από το αν επιλυθεί ή απορριφθεί.
Δεδομένου του promise.finally(onSettled), το callback onSettled δεν λαμβάνει τίποτα.
Αυτό θα επιστρέφει πάντα μια νέα "αλυσιδωτή" υπόσχεση.
Η επιστροφή ενός value από το finally αντιγράφει την κατάσταση και την τιμή από την αρχική υπόσχεση, αγνοώντας το value.
Η ρίψη ενός reason στο finally απορρίπτει την "αλυσιδωτή" υπόσχεση, αντικαθιστώντας οποιαδήποτε κατάσταση και τιμή ή λόγο από την αρχική υπόσχεση.
Διάφορες από τις μεθόδους μαζί:
const myPromise = new Promise(function (resolve, reject) {
const sampleData = [2, 4, 6, 8];
const randomNumber = Math.round(Math.random() * 5);
if (sampleData[randomNumber]) {
resolve(sampleData[randomNumber]);
} else {
reject('Sampling did not result in a sample');
}
});
const finalPromise = myPromise
.then(function (sampled) {
// If the random number was 0, 1, 2, or 3, this will be
// reached and the number 2, 4, 6, or 8 will be logged.
console.log(`Sampled data: ${sampled}`);
return 'yay';
})
.catch(function (reason) {
// If the random number was 4 or 5, this will be reached and
// reason will be "An error occurred". The entire chain will
// then reject with an Error with the reason as message.
throw new Error(reason);
})
.finally(function () {
// This will always log after either the sampled data is
// logged or the error is raised.
console.log('Promise completed');
});
randomNumber είναι 0-3:
myPromise θα επιλυθεί με την τιμή 2, 4, 6, or 8
finalPromise θα επιλυθεί με την τιμή 'yay'
Sampled data: ...Promise completedrandomNumber είναι 4-5:
myPromise θα απορριφθεί με τον λόγο 'Sampling did not result in a sample'
finalPromise θα απορριφθεί με τον λόγο Error('Sampling did not result in a sample')
Promise completed"uncaught rejected promise: Error('Sampling did not result in a sample')"
Όπως φαίνεται παραπάνω, το reject λειτουργεί με μια συμβολοσειρά, και μια υπόσχεση μπορεί επίσης να απορριφθεί με ένα Error.
Αν η αλυσίδωση υποσχέσεων ή η γενική χρήση δεν είναι ξεκάθαρη, το tutorial στο MDN είναι μια καλή πηγή για να μελετήσεις.