Aktualizacja (2017)
Tutaj w 2017 obietnice są wbudowane w JavaScript, zostały dodane zgodnie ze specyfikacją ES2015 (wypełniacze są dostępne dla przestarzałych środowisk, takich jak IE8-IE11). Składnia, z którą poszli, wykorzystuje wywołanie zwrotne, które przekazujesz do Promise
konstruktora ( Promise
executora ), który otrzymuje funkcje do rozwiązywania / odrzucania obietnicy jako argumenty.
Po pierwsze, ponieważ async
teraz ma znaczenie w JavaScript (mimo że jest to słowo kluczowe tylko w niektórych kontekstach), zamierzam użyć later
jako nazwy funkcji, aby uniknąć nieporozumień.
Podstawowe opóźnienie
Używając natywnych obietnic (lub wiernego polyfill), wyglądałoby to tak:
function later(delay) {
return new Promise(function(resolve) {
setTimeout(resolve, delay);
});
}
Zauważ, że zakłada się, że wersja setTimeout
jest zgodna z definicją dla przeglądarek, w której setTimeout
nie przekazuje żadnych argumentów do wywołania zwrotnego, chyba że podasz je po interwale (może to nie być prawdą w środowiskach innych niż przeglądarka i nie było prawda w Firefoksie, ale jest teraz; jest prawdą w Chrome, a nawet w IE8).
Podstawowe opóźnienie z wartością
Jeśli chcesz, aby funkcja opcjonalnie przekazywała wartość rozdzielczości, w dowolnej dość nowoczesnej przeglądarce, która umożliwia podanie dodatkowych argumentów setTimeout
po opóźnieniu, a następnie przekazanie ich do wywołania zwrotnego po wywołaniu, możesz to zrobić (bieżące przeglądarki Firefox i Chrome; IE11 + , prawdopodobnie Edge; nie IE8 ani IE9, nie mam pojęcia o IE10):
function later(delay, value) {
return new Promise(function(resolve) {
setTimeout(resolve, delay, value);
});
}
Jeśli używasz funkcji strzałek ES2015 +, może to być bardziej zwięzłe:
function later(delay, value) {
return new Promise(resolve => setTimeout(resolve, delay, value));
}
lub nawet
const later = (delay, value) =>
new Promise(resolve => setTimeout(resolve, delay, value));
Opóźnienie z możliwością anulowania z wartością
Jeśli chcesz umożliwić anulowanie limitu czasu, nie możesz po prostu zwrócić obietnicy od later
, ponieważ obietnic nie można anulować.
Ale możemy łatwo zwrócić obiekt z cancel
metodą i akcesorium dla obietnicy i odrzucić obietnicę w przypadku anulowania:
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;
}
}
};
};
Przykład na żywo:
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);
Oryginalna odpowiedź z 2014 r
Zwykle będziesz mieć bibliotekę obietnic (taką, którą napiszesz sam, lub jedną z kilku dostępnych). Ta biblioteka zazwyczaj będzie zawierała obiekt, który możesz utworzyć i później „rozwiązać”, a obiekt ten będzie miał „obietnicę”, którą możesz z niego uzyskać.
Wtedy later
wyglądałby mniej więcej tak:
function later() {
var p = new PromiseThingy();
setTimeout(function() {
p.resolve();
}, 2000);
return p.promise();
}
W komentarzu do pytania zapytałem:
Czy próbujesz stworzyć własną bibliotekę obietnic?
i Ty powiedziałeś
Nie byłem, ale myślę, że teraz właśnie to próbowałem zrozumieć. Tak zrobiłaby biblioteka
Aby pomóc w zrozumieniu, oto bardzo podstawowy przykład, który nie jest zdalnie zgodny z obietnicą A: kopia na żywo
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Very basic promises</title>
</head>
<body>
<script>
(function() {
var PromiseThingy = (function() {
function triggerCallback(callback, promise) {
try {
callback(promise.resolvedValue);
}
catch (e) {
}
}
function Promise() {
this.callbacks = [];
}
Promise.prototype.then = function(callback) {
var thispromise = this;
if (!this.resolved) {
this.callbacks.push(callback);
}
else {
setTimeout(function() {
triggerCallback(callback, thispromise);
}, 0);
}
return this;
};
function PromiseThingy() {
this.p = new 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);
}
}
};
PromiseThingy.prototype.promise = function() {
return this.p;
};
return PromiseThingy;
})();
function later() {
var p = new PromiseThingy();
setTimeout(function() {
p.resolve();
}, 2000);
return p.promise();
}
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>