Atualização (2017)
Aqui em 2017, as Promises são integradas ao JavaScript, foram adicionadas pela especificação ES2015 (polyfills estão disponíveis para ambientes desatualizados como IE8-IE11). A sintaxe usada usa um retorno de chamada que você passa para o Promise
construtor (o Promise
executor ) que recebe as funções para resolver / rejeitar a promessa como argumentos.
Primeiro, como async
agora tem um significado em JavaScript (mesmo que seja apenas uma palavra-chave em certos contextos), vou usar later
como o nome da função para evitar confusão.
Atraso Básico
Usando promessas nativas (ou um polyfill fiel) ficaria assim:
function later(delay) {
return new Promise(function(resolve) {
setTimeout(resolve, delay);
});
}
Observe que isso pressupõe uma versão setTimeout
compatível com a definição de navegadores onde setTimeout
não passa nenhum argumento para o retorno de chamada, a menos que você os forneça após o intervalo (isso pode não ser verdade em ambientes sem navegador, e não costumava ser verdade no Firefox, mas agora; é verdade no Chrome e até mesmo no IE8).
Atraso básico com valor
Se quiser que sua função passe opcionalmente um valor de resolução, em qualquer navegador vagamente moderno que permite fornecer argumentos extras para setTimeout
após o atraso e, em seguida, passa-os para o retorno de chamada quando chamado, você pode fazer isso (Firefox e Chrome atuais; IE11 + , presumivelmente Edge; não IE8 ou IE9, nenhuma ideia sobre o 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);
*/
});
}
Se você estiver usando as funções de seta ES2015 +, isso pode ser mais conciso:
function later(delay, value) {
return new Promise(resolve => setTimeout(resolve, delay, value));
}
ou mesmo
const later = (delay, value) =>
new Promise(resolve => setTimeout(resolve, delay, value));
Atraso cancelável com valor
Se você deseja possibilitar o cancelamento do timeout, não pode simplesmente devolver uma promessa de later
, porque as promessas não podem ser canceladas.
Mas podemos facilmente retornar um objeto com um cancel
método e um acessador para a promessa e rejeitar a promessa no cancelamento:
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;
}
}
};
};
Exemplo ao vivo:
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);
Resposta Original de 2014
Normalmente, você terá uma biblioteca de promessas (uma que você mesmo escreve ou uma das várias por aí). Essa biblioteca geralmente terá um objeto que você pode criar e "resolver" posteriormente, e esse objeto terá uma "promessa" que você poderá obter dele.
Então later
, tenderia a ser algo assim:
function later() {
var p = new PromiseThingy();
setTimeout(function() {
p.resolve();
}, 2000);
return p.promise(); // Note we're not returning `p` directly
}
Em um comentário sobre a pergunta, perguntei:
Você está tentando criar sua própria biblioteca de promessas?
E você disse
Eu não estava, mas acho que agora é isso que eu estava tentando entender. É assim que uma biblioteca faria
Para ajudar nesse entendimento, aqui está um exemplo muito básico , que não é remotamente compatível com 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>