En ce qui concerne ces deux grandes sources: NZakas - Retourner les promesses dans les chaînes de promesses et Les promesses de MDN , j'aimerais poser la question suivante:
Chaque fois que nous renvoyons une valeur d'un gestionnaire d'exécution des promesses, comment cette valeur est-elle transmise à la nouvelle promesse renvoyée par ce même gestionnaire?
Par exemple,
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
let p2 = new Promise(function(resolve, reject) {
resolve(43);
});
let p3 = p1.then(function(value) {
// first fulfillment handler
console.log(value); // 42
return p2;
});
p3.then(function(value) {
// second fulfillment handler
console.log(value); // 43
});
Dans cet exemple, p2
Est une promesse. p3
Est également une promesse provenant du gestionnaire d'exécution de p1
. Cependant p2 !== p3
. Au lieu de cela, p2
Se résout comme par magie en 43
(Comment?) Et cette valeur est ensuite transmise au gestionnaire d'exécution de p3
. Même la phrase ici est déroutante.
Pourriez-vous m'expliquer ce qui se passe exactement ici? Je suis totalement confus sur ce concept.
Supposons que le lancement de then()
callback rejette la promesse de résultat avec un échec et que le retour de then()
callback remplisse la promesse de résultat avec une valeur de succès.
let p2 = p1.then(() => {
throw new Error('lol')
})
// p2 was rejected with Error('lol')
let p3 = p1.then(() => {
return 42
})
// p3 was fulfilled with 42
Mais parfois, même dans la suite, nous ne savons pas si nous avons réussi ou non. Nous avons besoin de plus de temps.
return checkCache().then(cachedValue => {
if (cachedValue) {
return cachedValue
}
// I want to do some async work here
})
Toutefois, si j’ai un travail asynchrone là-bas, il serait trop tard pour return
ou throw
, n’est-ce pas?
return checkCache().then(cachedValue => {
if (cachedValue) {
return cachedValue
}
fetchData().then(fetchedValue => {
// Doesn’t make sense: it’s too late to return from outer function by now.
// What do we do?
// return fetchedValue
})
})
C’est pourquoi les promesses ne seraient pas utiles si vous ne pouviez pas résoudre une autre promesse .
Cela ne signifie pas que, dans votre exemple, p2
devientp3
. Ce sont des objets Promise séparés. Cependant, en renvoyant p2
À partir de then()
qui produit p3
, Vous dites "Je veux que p3
Soit résolu en quelle que soit la résolution de p2
, qu'elle réussisse ou non "
Quant à comment cela se produit, cela dépend de la mise en oeuvre. En interne, vous pouvez penser à then()
comme créant une nouvelle promesse. L'implémentation sera capable de la remplir ou de la rejeter quand bon lui semble. Normalement, il le remplira ou le rejettera automatiquement à votre retour:
// Warning: this is just an illustration
// and not a real implementation code.
// For example, it completely ignores
// the second then() argument for clarity,
// and completely ignores the Promises/A+
// requirement that continuations are
// run asynchronously.
then(callback) {
// Save these so we can manipulate
// the returned Promise when we are ready
let resolve, reject
// Imagine this._onFulfilled is an internal
// queue of code to run after current Promise resolves.
this._onFulfilled.Push(() => {
let result, error, succeeded
try {
// Call your callback!
result = callback(this._result)
succeeded = true
} catch (err) {
error = err
succeeded = false
}
if (succeeded) {
// If your callback returned a value,
// fulfill the returned Promise to it
resolve(result)
} else {
// If your callback threw an error,
// reject the returned Promise with it
reject(error)
}
})
// then() returns a Promise
return new Promise((_resolve, _reject) => {
resolve = _resolve
reject = _reject
})
}
Encore une fois, il s’agit bien de pseudo-code mais montre l’idée derrière la façon dont then()
pourrait être implémenté dans les implémentations de Promise.
Si nous voulons ajouter un support pour la résolution à une promesse, nous avons juste besoin de modifier le code pour avoir une branche spéciale si le callback
que vous passez à then()
a retourné une promesse:
if (succeeded) {
// If your callback returned a value,
// resolve the returned Promise to it...
if (typeof result.then === 'function') {
// ...unless it is a Promise itself,
// in which case we just pass our internal
// resolve and reject to then() of that Promise
result.then(resolve, reject)
} else {
resolve(result)
}
} else {
// If your callback threw an error,
// reject the returned Promise with it
reject(error)
}
})
Permettez-moi de préciser à nouveau qu'il ne s'agit pas d'une implémentation réelle de Promise, mais présente de gros trous et des incompatibilités. Toutefois, cela devrait vous donner une idée intuitive de la manière dont les bibliothèques Promise implémentent la résolution en promesse. Une fois que vous êtes à l'aise avec l'idée, je vous recommanderais de jeter un coup d'œil à la façon dont les implémentations Promise réelles gérez ceci .
Fondamentalement p3
est return
- une autre promesse: p2
. Ce qui signifie le résultat de p2
sera transmis en tant que paramètre au prochain rappel then
, dans ce cas, il se résout en 43
.
Chaque fois que vous utilisez le mot-clé return
, vous transmettez le résultat en tant que paramètre au prochain rappel de then
.
let p3 = p1.then(function(value) {
// first fulfillment handler
console.log(value); // 42
return p2;
});
Votre code :
p3.then(function(value) {
// second fulfillment handler
console.log(value); // 43
});
Est égal à:
p1.then(function(resultOfP1) {
// resultOfP1 === 42
return p2; // // Returning a promise ( that might resolve to 43 or fail )
})
.then(function(resultOfP2) {
console.log(resultOfP2) // '43'
});
Btw, j'ai remarqué que vous utilisez la syntaxe ES6, vous pouvez avoir une syntaxe plus claire en utilisant la syntaxe grosse flèche:
p1.then(resultOfP1 => p2) // the `return` is implied since it's a one-liner
.then(resultOfP2 => console.log(resultOfP2));
Dans cet exemple, p2 est une promesse. p3 est également une promesse provenant du gestionnaire d'exécution de p1. Cependant p2! == p3. Au lieu de cela, p2 se résout magiquement à 43 (comment?) Et cette valeur est ensuite transmise au gestionnaire d'exécution de p3. Même la phrase ici est déroutante.
une version simplifiée comment ça marche (pseudo-code seulement)
function resolve(value){
if(isPromise(value)){
value.then(resolve, reject);
}else{
//dispatch the value to the listener
}
}
le tout est bien plus compliqué puisque vous devez faire attention, si la promesse a déjà été résolue et quelques autres choses.
Je vais essayer de répondre à la question "Pourquoi then
callbacks peuvent renvoyer Promise
s eux-mêmes" plus canoniques. Pour prendre un angle différent, je compare Promise
s à un type de conteneur moins complexe et prêtant à confusion - Array
s.
Un Promise
est un conteneur pour une valeur future. Un Array
est un conteneur pour un nombre arbitraire de valeurs.
Nous ne pouvons pas appliquer les fonctions normales aux types de conteneurs:
const sqr = x => x * x;
const xs = [1,2,3];
const p = Promise.resolve(3);
sqr(xs); // fails
sqr(p); // fails
Nous avons besoin d'un mécanisme pour les placer dans le contexte d'un conteneur spécifique:
xs.map(sqr); // [1,4,9]
p.then(sqr); // Promise {[[PromiseValue]]: 9}
Mais que se passe-t-il lorsque la fonction fournie renvoie elle-même un conteneur du même type?
const sqra = x => [x * x];
const sqrp = x => Promise.resolve(x * x);
const xs = [1,2,3];
const p = Promise.resolve(3);
xs.map(sqra); // [[1],[4],[9]]
p.then(sqrp); // Promise {[[PromiseValue]]: 9}
sqra
agit comme prévu. Il renvoie simplement un conteneur imbriqué avec les valeurs correctes. Ce n'est évidemment pas très utile cependant.
Mais comment interpréter le résultat de sqrp
? Si nous suivons notre propre logique, ce devait être quelque chose comme Promise {[[PromiseValue]]: Promise {[[PromiseValue]]: 9}}
- mais ce n'est pas. Alors, quelle magie se passe ici?
Pour reconstruire le mécanisme, nous devons simplement adapter un peu notre méthode map
:
const flatten = f => x => f(x)[0];
const sqra = x => [x * x];
const sqrp = x => Promise.resolve(x * x);
const xs = [1,2,3];
xs.map(flatten(sqra))
flatten
prend juste une fonction et une valeur, applique la fonction à la valeur et décompresse le résultat, réduisant ainsi la structure d'un tableau imbriqué d'un niveau.
Autrement dit, then
dans le contexte de Promise
s équivaut à map
combiné à flatten
dans le contexte de Array
s. Ce comportement est extrêmement important. Nous pouvons appliquer non seulement des fonctions normales à un Promise
mais également des fonctions qui retournent elles-mêmes un Promise
.
En fait, c'est le domaine de la programmation fonctionnelle. Un Promise
est une implémentation spécifique d'un monad, then
est bind
/chain
et une fonction qui retourne un Promise
est une fonction monadique. Lorsque vous comprenez l'API Promise
, vous comprenez en gros toutes les monades.