Dies ist kein reales Problem, ich versuche nur zu verstehen, wie Versprechen entstehen.
Ich muss verstehen, wie man ein Versprechen für eine Funktion macht, die nichts zurückgibt, wie setTimeout.
Angenommen, ich habe:
function async(callback){
setTimeout(function(){
callback();
}, 5000);
}
async(function(){
console.log('async called back');
});
Wie erstelle ich ein Versprechen, das async
zurückgeben kann, nachdem setTimeout
für callback()
bereit ist?
Ich nahm an, dass das Einwickeln irgendwohin führen würde:
function setTimeoutReturnPromise(){
function promise(){}
promise.prototype.then = function() {
console.log('timed out');
};
setTimeout(function(){
return ???
},2000);
return promise;
}
Aber darüber hinaus kann ich nicht nachdenken.
Hier sind Versprechen 2017 in JavaScript integriert. Sie wurden durch die ES2015-Spezifikation hinzugefügt (Polyfills sind für veraltete Umgebungen wie IE8-IE11 verfügbar). Die verwendete Syntax verwendet einen Rückruf, den Sie an den Konstruktor Promise
übergeben (den Promise
executor), der die Funktionen zum Auflösen/Zurückweisen des Versprechens als Argumente empfängt .
Erstens, da async
jetzt eine Bedeutung in JavaScript hat (obwohl es in bestimmten Kontexten nur ein Schlüsselwort ist), werde ich later
als Namen von verwenden die Funktion, um Verwirrung zu vermeiden.
Mit einheimischen Versprechungen (oder einer treuen Polyfüllung) würde es so aussehen:
function later(delay) {
return new Promise(function(resolve) {
setTimeout(resolve, delay);
});
}
Beachten Sie, dass dies eine Version von setTimeout
voraussetzt, die mit der Definition für Browser kompatibel ist, wobei setTimeout
keine Argumente an den Rückruf übergibt, es sei denn, Sie geben sie nach dem Intervall an (Dies mag in Nicht-Browser-Umgebungen nicht zutreffen und war in Firefox früher nicht zutreffend, ist es jetzt aber; es gilt für Chrome und sogar für IE8).
Wenn Sie möchten, dass Ihre Funktion optional einen Auflösungswert übergibt, können Sie dies in einem vage modernen Browser tun, der es Ihnen ermöglicht, setTimeout
nach der Verzögerung zusätzliche Argumente zu übergeben und diese dann beim Aufruf an den Rückruf weiterzuleiten ( aktueller Firefox und Chrome; IE11 +, vermutlich Edge; nicht IE8 oder IE9, keine Ahnung von 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);
*/
});
}
Wenn Sie Pfeilfunktionen für ES2015 + verwenden, kann dies präziser sein:
function later(delay, value) {
return new Promise(resolve => setTimeout(resolve, delay, value));
}
oder auch
const later = (delay, value) =>
new Promise(resolve => setTimeout(resolve, delay, value));
Wenn Sie das Abbrechen des Timeouts ermöglichen möchten, können Sie nicht einfach ein Versprechen von later
zurückgeben, da Versprechen nicht storniert werden können.
Wir können jedoch problemlos ein Objekt mit einer cancel
-Methode und einem Accessor für das Versprechen zurückgeben und das Versprechen beim Abbrechen ablehnen:
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;
}
}
};
};
Live Beispiel:
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);
Normalerweise haben Sie eine Versprechensbibliothek (eine, die Sie selbst geschrieben haben, oder eine der mehreren, die es gibt). Diese Bibliothek enthält normalerweise ein Objekt, das Sie erstellen und später "auflösen" können, und dieses Objekt enthält ein "Versprechen", das Sie von ihm erhalten können.
Dann würde later
ungefähr so aussehen:
function later() {
var p = new PromiseThingy();
setTimeout(function() {
p.resolve();
}, 2000);
return p.promise(); // Note we're not returning `p` directly
}
In einem Kommentar zu der Frage fragte ich:
Versuchen Sie, Ihre eigene Versprechen-Bibliothek zu erstellen?
und du sagtest
Ich war es nicht, aber ich denke, das ist es, was ich zu verstehen versuchte. So würde es eine Bibliothek machen
Um dieses Verständnis zu erleichtern, ist hier ein sehr, sehr einfach Beispiel, das nicht mit Promises-A kompatibel ist: 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>