【问题标题】:How do you wrap setTimeout in a promise [duplicate]你如何将 setTimeout 包装在一个承诺中[重复]
【发布时间】: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); 但这并没有改变任何东西。

标签: javascript promise


【解决方案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>

    【讨论】:

      猜你喜欢
      • 2016-08-11
      • 2017-07-18
      • 2020-08-26
      • 1970-01-01
      • 1970-01-01
      • 2016-04-21
      • 2021-04-01
      • 2019-12-29
      • 2018-04-23
      相关资源
      最近更新 更多