你如何将 setTimeout 包装在一个承诺中[重复]
Posted
技术标签:
【中文标题】你如何将 setTimeout 包装在一个承诺中[重复]【英文标题】:How do you wrap setTimeout in a promise [duplicate] 【发布时间】:2016-02-23 21:49:32 【问题描述】:我正在尝试为返回承诺的对象运行测试套件。我想将几个动作与它们之间的短暂超时链接在一起。我认为返回 promise 的“then”调用会等待 promise 完成,然后再触发下一个链式 then 调用。
我创建了一个函数
function promiseTimeout (time)
return new Promise(function(resolve,reject)
setTimeout(function()resolve(time);,time);
);
;
尝试将 setTimeout 包装在 Promise 中。
然后在我的测试套件中,我正在调用这样的东西......
it('should restore state when browser back button is used',function(done)
r.domOK().then(function()
xh.fire('akc-route-change','/user/4/profile/new');
).then(promiseTimeout(2000)).then(function(t)
xu.fire('akc-route-change','/user/6');
).then(promiseTimeout(10)).then(function(t)
expect(xu.params[0]).to.equal(6);
history.back();
).then(promiseTimeout(10)).then(function()
expect(xu.params[0]).to.equal(4);
done();
);
);
我可以在第一个 xh.fire
调用上放置一个断点,在 xu.fire
调用上放置第二个断点,并且当 a 从第一个断点继续到第二个断点时,我预计会有两秒的间隙。
而是立即到达第二个断点,此时t
的值是未定义的。
我做错了什么?
【问题讨论】:
你所做的类似于setTimeout(fn(), 1000)
而不是setTimeout(fn, 1000)
,then
采用的是一个返回承诺而不是承诺的函数。
另外,你可以从it
中得到return
的promise,不需要使用done
不明白你的意思我只是将 setTimout 调用更改为 setTimeout(resolve,time,time);
但这并没有改变任何东西。
【参考方案1】:
TL;DR - 你已经正确地将 setTimeout 包装在一个 Promise 中,问题是你使用不当
.then(promiseTimeout(2000)).then
不会做你所期望的。 .then 的“签名”是then(functionResolved, functionRejected)
promise 的 then 方法接受两个参数:
promise.then(onFulfilled, onRejected)
onFulfilled 和 onRejected 都是可选参数:
如果 onFulfilled 不是函数,则必须忽略它。 如果 onRejected 不是函数,则必须将其忽略。
来源:https://promisesaplus.com/#point-21
你没有向 then 传递函数
考虑一下你的做法:
Promise.resolve('hello')
.then(promiseTimeout(2000))
.then(console.log.bind(console))
vs 应该怎么做:
Promise.resolve('hello').then(function()
return promiseTimeout(2000)
).then(console.log.bind(console))
第一个立即输出'hello'
2秒后第二个输出2000
因此,你应该这样做:
it('should restore state when browser back button is used', function(done)
r.domOK().then(function()
xh.fire('akc-route-change', '/user/4/profile/new');
).then(function()
return promiseTimeout(2000);
).then(function(t)
xu.fire('akc-route-change', '/user/6');
).then(function()
return promiseTimeout(10);
).then(function(t)
expect(xu.params[0]).to.equal(6);
history.back();
).then(function()
return promiseTimeout(10);
).then(function()
expect(xu.params[0]).to.equal(4);
done();
);
);
或者:
it('should restore state when browser back button is used', function(done)
r.domOK().then(function()
xh.fire('akc-route-change', '/user/4/profile/new');
).then(promiseTimeout.bind(null, 2000)
).then(function(t)
xu.fire('akc-route-change', '/user/6');
).then(promiseTimeout.bind(null, 10)
).then(function(t)
expect(xu.params[0]).to.equal(6);
history.back();
).then(promiseTimeout.bind(null, 10)
).then(function()
expect(xu.params[0]).to.equal(4);
done();
);
);
编辑:2019 年 3 月
多年来,情况发生了很大变化 - 箭头符号使这变得更加容易
首先,我会以不同的方式定义 promiseTimeout
const promiseTimeout = time => () => new Promise(resolve => setTimeout(resolve, time, time));
上面返回一个函数,可以调用该函数来创建“承诺延迟”并解析为时间(延迟长度)。考虑到这一点,我不明白为什么这会非常有用,而是我会:
const promiseTimeout = time => result => new Promise(resolve => setTimeout(resolve, time, result));
以上将解决上一个承诺的结果(更有用)
但它是一个返回函数的函数,因此原始代码的其余部分可以保持不变。然而,关于原始代码的问题是,不需要将任何值传递到 .then 链中,因此更简单
const promiseTimeout = time => () => new Promise(resolve => setTimeout(resolve, time));
问题的it
块中的原始代码现在可以使用不变
it('should restore state when browser back button is used',function(done)
r.domOK().then(function()
xh.fire('akc-route-change','/user/4/profile/new');
).then(promiseTimeout(2000)).then(function()
xu.fire('akc-route-change','/user/6');
).then(promiseTimeout(10)).then(function()
expect(xu.params[0]).to.equal(6);
history.back();
).then(promiseTimeout(10)).then(function()
expect(xu.params[0]).to.equal(4);
done();
);
);
【讨论】:
知道了。就像第二种方法一样,虽然我只是尝试了两种方法,但我也整理了 promiseTimeout - 就像对 Benjamin Gruenbaum 的评论一样 是的,这很好用,但次要的一点是,在第一个代码片段中,一遍又一遍地执行function() return promise.Timeout(n);
似乎过于冗长,想只写makePromiseTimeout(n)
。
同样的问题 worth reading here,有更多的 ES6 语法和 /w 并且没有原始承诺传递。【参考方案2】:
要使超时工作如您所愿,请编写一个需要延迟的函数,并返回一个适合传递给then
的函数。
function timeout(ms)
return () => new Promise(resolve => setTimeout(resolve, ms));
像这样使用它:
Promise.resolve() . then(timeout(1000)) . then(() => console.log("got here"););
但是,您可能希望访问导致超时的承诺的已解决值。在这种情况下,安排timeout()
创建的函数传递值:
function timeout(ms)
return value => new Promise(resolve => setTimeout(() => resolve(value), ms));
像这样使用它:
Promise.resolve(42) . then(timeout(1000)) . then(value => console.log(value));
【讨论】:
我不明白这个答案。去掉新的 ES6 符号,并将 timeout 重命名为 promiseTimeout 答案的第一部分似乎与我原来的问题相同,但它不起作用。区别在哪里? 我的timeout
函数返回一个返回承诺的函数。你原来的回报一个承诺。 then
需要一个函数——像你做的那样向它传递一个承诺根本不会做任何事情。
抱歉之前的评论 - 我现在明白你在说什么了。如果没有仔细考虑并将其有效地翻译回我理解的 javascript,我发现这个新的 ES6 符号有点难以理解。
这非常简洁,但在我的代码中,测试在超时触发和实际测试运行之前返回成功。测试失败会产生错误。我将其更改为:'return Promise.resolve(42).then...' 现在测试正确报告成功/失败。
为什么需要第一个Promise.resolve().then
?为什么不让你的timeout
返回new Promise(...)
?【参考方案3】:
上面已经回答了这个问题,但我觉得可以通过以下方式轻松完成:
const setTimeoutPromise = ms => new Promise(resolve => setTimeout(resolve, ms))
setTimeoutProise
函数接受ms
中的等待时间并将其传递给setTimeout
函数。一旦等待时间结束,传递给 promise 的 resolve 方法就会被执行。
可以这样使用:
setTimeoutPromise(3000).then(doSomething)
【讨论】:
【参考方案4】:await new Promise((resolve, reject)=>
// wait for 50 ms.
setTimeout(function()resolve(), 50);
);
console.log("This will appear after waiting for 50 ms");
这可以在异步函数中使用,执行将等到给定的时间间隔。
【讨论】:
虽然此代码可以回答问题,但提供有关 如何 和 为什么 解决问题的附加上下文将提高答案的长期价值。 这对于执行 await 样式获取链非常有用,因此如果您将其放在 const res = await fetch( someURL ) 之上,它会像魅力一样延迟它,没有多余的绒毛。【参考方案5】:另一种为Promise
s 添加延迟无需预定义或import
辅助函数(我个人最喜欢的方法)是扩展Promise
构造函数的属性:
Promise.prototype.delay = function (ms)
return new Promise(resolve =>
window.setTimeout(this.then.bind(this, resolve), ms);
);
我省略了reject
回调,因为这意味着总是resolve
。
演示
Promise.prototype.delay = function(ms)
console.log(`[log] Fetching data in $ms / 1000 second(s)...`);
return new Promise(resolve =>
window.setTimeout(this.then.bind(this, resolve), ms);
);
document.getElementById('fetch').onclick = function()
const duration = 1000 * document.querySelector('#duration[type="number"]').value;
// Promise.resolve() returns a Promise
// and this simply simulates some long-running background processes
// so we can add some delays on it
Promise
.resolve('Some data from server.')
.delay(duration)
.then(console.log);
<div>
<input id="duration" type="number" value="3" />
<button id="fetch">Fetch data from server</button>
</div>
或者,如果您还需要它是 .catch()
-able,那么此时您需要第二个参数 (reject
)。注意catch()
的处理也会在延迟之后发生:
Promise.prototype.delay = function(ms)
console.log(`[log] Fetching data in $ms / 1000 second(s)...`);
return new Promise((resolve, reject) =>
window.setTimeout(() =>
this.then(resolve).catch(reject);
, ms);
);
document.getElementById('fetch').onclick = function()
const duration = 1000 * document.querySelector('#duration[type="number"]').value;
Promise
.reject('Some data from server.')
.delay(duration)
.then(console.log)
.catch(console.log); // Promise rejection or failures will always end up here
<div>
<input id="duration" type="number" value="3" />
<button id="fetch">Fetch data from server</button>
</div>
【讨论】:
以上是关于你如何将 setTimeout 包装在一个承诺中[重复]的主要内容,如果未能解决你的问题,请参考以下文章