如何在承诺中包装setTimeout

时间:2015-11-21 12:00:02

标签: javascript promise

我正在尝试为返回promise的对象运行测试套件。我希望将几个动作连在一起,并在它们之间进行短暂的超时。我认为那是"然后"在发出下一个链接然后调用之前,返回承诺的调用将等待承诺完成。

我创建了一个函数

function promiseTimeout (time) {
  return new Promise(function(resolve,reject){
    setTimeout(function(){resolve(time);},time);
  });
};

尝试在承诺中包装setTimeout。

然后在我的测试套件中,我正在调用这样的东西......

    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呼叫上设置第二个断点,并且当从第一个断点继续到第二个断点时,预计会有两秒的间隙。

而是立即到达第二个断点,此时t的值未定义。

我做错了什么?

5 个答案:

答案 0 :(得分:15)

TL; DR - 你已经在一个承诺中正确包装了setTimeout,问题是你使用它不正确

.then(promiseTimeout(2000)).then

不会做你期望的。 .then的“签名”是then(functionResolved, functionRejected)

  

promise的then方法接受两个参数:

     

promise.then(onFulfilled,onRejected)

     

onFulfilled和onRejected都是可选参数:

     
      
  • 如果onFulfilled不是函数,则必须忽略它。
  •   
  • 如果onRejected不是函数,则必须忽略它。
  •   

来源:https://promisesaplus.com/#point-21

您没有将功能传递给

考虑一下你的做法:

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

第一个输出'你好'立即

第二次输出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));

上面返回一个函数,可以调用该函数来创建“promise delay”并解析为时间(延迟的长度)。考虑到这一点,我不明白为什么那会非常有用,而不是我:

const promiseTimeout = time => result => new Promise(resolve => setTimeout(resolve, time, result));

以上将解决前一个承诺的结果(更有用)

但它是一个返回函数的函数,因此ORIGINAL代码的其余部分可以保持不变。然而,关于原始代码的事情是,不需要在.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();
  });
});

答案 1 :(得分:5)

要使超时工作正常,请编写一个延迟的函数,并返回一个适合传递给then的函数。

function timeout(ms) {
  return () => new Promise(resolve => setTimeout(resolve, ms));
}

像这样使用:

Promise.resolve() . then(timeout(1000)) . then(() => console.log("got here"););

但是,您可能希望访问导致超时的promise的已解析值。在这种情况下,安排由timeout()创建的函数来传递值:

function timeout(ms) {
  return value => new Promise(resolve => setTimeout(() => resolve(value), ms));
}

像这样使用:

Promise.resolve(42) . then(timeout(1000)) . then(value => console.log(value));

答案 2 :(得分:4)

await new Promise((resolve, reject)=>{
    // wait for 50 ms.
    setTimeout(function(){resolve()}, 50);
});
console.log("This will appear after waiting for 50 ms");

这可以在异步函数中使用,执行将等到给定的间隔。

答案 3 :(得分:1)

正如上面已经回答的那样,但是我认为可以轻松地做到这一点:

const setTimeoutPromise = ms => new Promise(resolve => setTimeout(resolve, ms))

setTimeoutProise函数在ms中接受等待时间,并将其传递给setTimeout函数。等待时间结束后,将执行传递给promise的resolve方法。

可以这样使用:

setTimeoutPromise(3000).then(doSomething)

答案 4 :(得分:0)

另一种在没有预先定义或Promise辅助功能(我个人最喜欢)的情况下为import添加延迟的方法是扩展{ {1}}构造函数:

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())的时候。请注意,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
}