Ce n'est pas un problème du monde réel, j'essaie simplement de comprendre comment les promesses sont créées.
J'ai besoin de comprendre comment faire une promesse pour une fonction qui ne retourne rien, comme setTimeout.
Supposons que j'ai:
function async(callback){
setTimeout(function(){
callback();
}, 5000);
}
async(function(){
console.log('async called back');
});
Comment créer une promesse que async
peut retourner après que setTimeout
soit prêt à callback()
?
Je supposais que ça me mènerait quelque part:
function setTimeoutReturnPromise(){
function promise(){}
promise.prototype.then = function() {
console.log('timed out');
};
setTimeout(function(){
return ???
},2000);
return promise;
}
Mais je ne peux pas penser au-delà de cela.
Ici en 2017, les promesses sont intégrées à JavaScript, elles ont été ajoutées par la spécification ES2015 (les polyfill sont disponibles pour les environnements obsolètes tels que IE8-IE11). La syntaxe utilisée correspond au rappel que vous passez dans le constructeur Promise
(le Promise
exécuteur) qui reçoit les fonctions permettant de résoudre/rejeter la promesse sous forme d'arguments .
Premièrement, puisque async
a maintenant une signification en JavaScript (même s’il ne s’agit que d’un mot-clé dans certains contextes), je vais utiliser later
comme nom de la fonction pour éviter la confusion.
En utilisant des promesses indigènes (ou un polyfill fidèle), cela ressemblerait à ceci:
function later(delay) {
return new Promise(function(resolve) {
setTimeout(resolve, delay);
});
}
Notez que cela suppose une version de setTimeout
conforme à la définition des navigateurs où setTimeout
ne transmet aucun argument au rappel, à moins que vous ne le leur donniez après l'intervalle. (Ce n'est peut-être pas le cas dans les environnements autres que les navigateurs, et ce n'était pas le cas auparavant avec Firefox, mais c'est le cas maintenant; c'est vrai pour Chrome et même de retour sur IE8).
Si vous souhaitez que votre fonction transmette éventuellement une valeur de résolution, sur tout navigateur vaguement moderne, vous permettant de donner des arguments supplémentaires à setTimeout
après le délai, puis de les transmettre au rappel lors de l'appel, vous pouvez le faire ( Firefox et Chrome actuels; IE11 +, vraisemblablement Edge; pas IE8 ou IE9, aucune idée de IE10):
function later(delay, value) {
return new Promise(function(resolve) {
setTimeout(resolve, delay, value); // Note the order, `delay` before `value`
/* Or for outdated browsers that don't support doing that:
setTimeout(function() {
resolve(value);
}, delay);
Or alternately:
setTimeout(resolve.bind(null, value), delay);
*/
});
}
Si vous utilisez les fonctions de flèche ES2015 +, cela peut être plus concis:
function later(delay, value) {
return new Promise(resolve => setTimeout(resolve, delay, value));
}
ou même
const later = (delay, value) =>
new Promise(resolve => setTimeout(resolve, delay, value));
Si vous voulez rendre possible l'annulation du délai, vous ne pouvez pas simplement renvoyer une promesse de later
, car les promesses ne peuvent pas être annulées.
Mais nous pouvons facilement retourner un objet avec une méthode cancel
et un accesseur pour la promesse, et rejeter la promesse sur cancel:
const later = (delay, value) => {
let timer = 0;
let reject = null;
const promise = new Promise((resolve, _reject) => {
reject = _reject;
timer = setTimeout(resolve, delay, value);
});
return {
get promise() { return promise; },
cancel() {
if (timer) {
clearTimeout(timer);
timer = 0;
reject();
reject = null;
}
}
};
};
Exemple en direct:
const later = (delay, value) => {
let timer = 0;
let reject = null;
const promise = new Promise((resolve, _reject) => {
reject = _reject;
timer = setTimeout(resolve, delay, value);
});
return {
get promise() { return promise; },
cancel() {
if (timer) {
clearTimeout(timer);
timer = 0;
reject();
reject = null;
}
}
};
};
const l1 = later(100, "l1");
l1.promise
.then(msg => { console.log(msg); })
.catch(() => { console.log("l1 cancelled"); });
const l2 = later(200, "l2");
l2.promise
.then(msg => { console.log(msg); })
.catch(() => { console.log("l2 cancelled"); });
setTimeout(() => {
l2.cancel();
}, 150);
Habituellement, vous aurez une bibliothèque de promesses (une que vous écrivez vous-même ou l’une des nombreuses disponibles). Cette bibliothèque aura généralement un objet que vous pourrez créer et "résoudre" plus tard, et cet objet aura une "promesse" que vous pourrez obtenir.
Alors, later
aurait tendance à ressembler à ceci:
function later() {
var p = new PromiseThingy();
setTimeout(function() {
p.resolve();
}, 2000);
return p.promise(); // Note we're not returning `p` directly
}
Dans un commentaire sur la question, j'ai demandé:
Essayez-vous de créer votre propre bibliothèque de promesses?
et vous avez dit
Je n'étais pas mais je suppose que c'est ce que j'essayais de comprendre. C'est comme ça qu'une bibliothèque le ferait
Pour faciliter cette compréhension, voici un exemple très très basique, qui n'est pas distant. Conforme à Promises-A: Live Copy
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Very basic promises</title>
</head>
<body>
<script>
(function() {
// ==== Very basic promise implementation, not remotely Promises-A compliant, just a very basic example
var PromiseThingy = (function() {
// Internal - trigger a callback
function triggerCallback(callback, promise) {
try {
callback(promise.resolvedValue);
}
catch (e) {
}
}
// The internal promise constructor, we don't share this
function Promise() {
this.callbacks = [];
}
// Register a 'then' callback
Promise.prototype.then = function(callback) {
var thispromise = this;
if (!this.resolved) {
// Not resolved yet, remember the callback
this.callbacks.Push(callback);
}
else {
// Resolved; trigger callback right away, but always async
setTimeout(function() {
triggerCallback(callback, thispromise);
}, 0);
}
return this;
};
// Our public constructor for PromiseThingys
function PromiseThingy() {
this.p = new Promise();
}
// Resolve our underlying promise
PromiseThingy.prototype.resolve = function(value) {
var n;
if (!this.p.resolved) {
this.p.resolved = true;
this.p.resolvedValue = value;
for (n = 0; n < this.p.callbacks.length; ++n) {
triggerCallback(this.p.callbacks[n], this.p);
}
}
};
// Get our underlying promise
PromiseThingy.prototype.promise = function() {
return this.p;
};
// Export public
return PromiseThingy;
})();
// ==== Using it
function later() {
var p = new PromiseThingy();
setTimeout(function() {
p.resolve();
}, 2000);
return p.promise(); // Note we're not returning `p` directly
}
display("Start " + Date.now());
later().then(function() {
display("Done1 " + Date.now());
}).then(function() {
display("Done2 " + Date.now());
});
function display(msg) {
var p = document.createElement('p');
p.innerHTML = String(msg);
document.body.appendChild(p);
}
})();
</script>
</body>
</html>