你如何将 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】:

另一种为Promises 添加延迟无需预定义或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 包装在一个承诺中[重复]的主要内容,如果未能解决你的问题,请参考以下文章

如何从 setTimeout 做出承诺

在承诺链上使用 setTimeout

在 JavaScript 中,如何在超时中包装承诺?

承诺与 setTimeout

为啥 Typescript 认为 async/await 返回包装在承诺中的值?

javascript 来自setTimeout的承诺