如何从 setTimeout 做出承诺

这不是现实世界的问题,我只是试图理解承诺是如何产生的。

我需要了解如何为一个不返回任何值的函数(如 setTimeout)做出承诺。

假设我有:

function async(callback){
setTimeout(function(){
callback();
}, 5000);
}


async(function(){
console.log('async called back');
});

我如何创建一个承诺,async可以返回后,setTimeout是准备 callback()

我以为包装起来会带我去某个地方:

function setTimeoutReturnPromise(){


function promise(){}


promise.prototype.then = function() {
console.log('timed out');
};


setTimeout(function(){
return ???
},2000);




return promise;
}

但我不能想得太远。

162815 次浏览

更新(2017年)

在2017年,承诺被内置到 JavaScript 中,它们是根据 ES2015规范添加的(多填充可用于过时的环境,如 IE8-IE11)。它们所使用的语法使用传递到 Promise构造函数(Promise 遗嘱执行人)的回调函数,Promise构造函数接收将承诺作为参数进行解析/拒绝的函数。

首先,由于 async现在在 JavaScript 中有了意义(即使它只是某些上下文中的关键字) ,我将使用 later作为函数的名称,以避免混淆。

基本延迟

使用原生的承诺(或者一个忠实的填充) ,它看起来像这样:

function later(delay) {
return new Promise(function(resolve) {
setTimeout(resolve, delay);
});
}

请注意,这里假设 setTimeout版本与 浏览器的定义兼容,setTimeout不会向回调传递任何参数,除非你在间隔之后给它们(这在非浏览器环境中可能不是真的,在 Firefox 上过去不是,但现在是; 在 Chrome 上是真的,甚至在 IE8上也是真的)。

带值的基本延迟

如果你想让你的函数有选择地传递一个分辨率值,在任何一个模糊的现代浏览器上,允许你在延迟后给 setTimeout额外的参数,然后在调用时传递给回调,你可以这样做(当前的 Firefox 和 Chrome; IE11 + ,可能是 Edge; 没有 IE8或 IE9,不知道 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);
*/
});
}

如果你使用 ES2015 + 箭头函数,那可以更简洁:

function later(delay, value) {
return new Promise(resolve => setTimeout(resolve, delay, value));
}

甚至

const later = (delay, value) =>
new Promise(resolve => setTimeout(resolve, delay, value));

有值可取消延迟

如果希望取消超时,则不能仅仅从 later返回承诺,因为承诺不能被取消。

但是我们可以很容易地返回一个带有 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;
}
}
};
};

示例:

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);


2014年的原始答案

通常你会有一个承诺库(一个你自己写的,或者其他几个库中的一个)。这个库通常有一个您可以创建的对象,然后再“解析”,这个对象将有一个您可以从中获得的“承诺”。

那么 later就会倾向于看起来像这样:

function later() {
var p = new PromiseThingy();
setTimeout(function() {
p.resolve();
}, 2000);


return p.promise(); // Note we're not returning `p` directly
}

在回答这个问题时,我问道:

您是否正在尝试创建自己的承诺库?

然后你说

我以前不是,但现在我想这才是我想要理解的。图书馆就是这么做的

为了帮助理解这一点,这里有一个 非常非常简单示例,它不是远程承诺-一个兼容的: 收到

<!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>

这不是最初问题的答案。但是,作为一个原始问题不是一个现实世界的问题,它不应该是一个问题。我试图向一个朋友解释什么是 JavaScript 中的承诺,以及承诺和回调之间的区别。

下面的代码作为一种解释:

//very basic callback example using setTimeout
//function a is asynchronous function
//function b used as a callback
function a (callback){
setTimeout (function(){
console.log ('using callback:');
let mockResponseData = '{"data": "something for callback"}';
if (callback){
callback (mockResponseData);
}
}, 2000);


}


function b (dataJson) {
let dataObject = JSON.parse (dataJson);
console.log (dataObject.data);
}


a (b);


//rewriting above code using Promise
//function c is asynchronous function
function c () {
return new Promise(function (resolve, reject) {
setTimeout (function(){
console.log ('using promise:');
let mockResponseData = '{"data": "something for promise"}';
resolve(mockResponseData);
}, 2000);
});


}


c().then (b);

JsFiddle

const setTimeoutAsync = (cb, delay) =>
new Promise((resolve) => {
setTimeout(() => {
resolve(cb());
}, delay);
});

我们可以像这样传递定制的‘ cb fxn’

实施方法:

// Promisify setTimeout
const pause = (ms, cb, ...args) =>
new Promise((resolve, reject) => {
setTimeout(async () => {
try {
resolve(await cb?.(...args))
} catch (error) {
reject(error)
}
}, ms)
})

测试:

// Test 1
pause(1000).then(() => console.log('called'))
// Test 2
pause(1000, (a, b, c) => [a, b, c], 1, 2, 3).then(value => console.log(value))
// Test 3
pause(1000, () => {
throw Error('foo')
}).catch(error => console.error(error))

从节点 v15开始,您可以使用 计时器承诺 API

来自文件的例子:

import { setTimeout } from 'timers/promises'


const res = await setTimeout(100, 'result')


console.log(res)  // Prints 'result'

它使用 signals非常像浏览器 fetch处理中止,查看文档了解更多:)

最简单的方法

(async function() {
console.log('1');
    

await SleepJs(3000);
    

console.log('2');
} )();


function SleepJs(delay) {
return new Promise(function(resolve) {
setTimeout(resolve, delay);
});
}

围绕 setTimeout包装承诺的一行程序

await new Promise(r => setTimeout(r, ms))

例如:

async someFunction() {
// Do something


// Wait 2 seconds
await new Promise(r => setTimeout(r, 2000))


// Do something else
}

如果这些方法对你都不管用,请试试这个

const asyncTimeout = (ms) => {
// when you make a promise you have to resolve it or reject it
// if you are like me that didn't get promises at all read the docs
return new Promise((resolve, reject) => {
setTimeout(() => {
const users = [
{ id: 1, name: 'Pablo' },
{ id: 2, name: 'Pedro' }
]
resolve(users) // this returns users
}, ms)
})
}


(async () => {
const obj = await asyncTimeout(3000)
console.log(obj)
})()