【问题标题】:Bluebird promises freeze when using Sinon's fake timer蓝鸟承诺在使用诗乃的假计时器时冻结
【发布时间】:2015-07-14 10:15:47
【问题描述】:

以下测试在与诗乃的假计时器和蓝鸟一起使用时会冻结。

var sinon = require('sinon');
var Promise = require('bluebird');

describe('failing test', function() {
  beforeEach(function() {
    this.clock = sinon.useFakeTimers();
  });
  afterEach(function() {
    this.clock.restore();
  });
  it('test', function(done) {
    Promise.delay(1000).then(function(){
        done(); //This never gets called     
    });
  });
});

我正在将 Mocha (v2.2.5) 与 Bluebird (v2.9.33) 和 Sinon (v1.15.3) 一起使用。

我尝试了 Bluebird 和 Sinon 的一些讨论中提供的建议,但我无法完成这项工作。这似乎是 Sinon 存根 setImmediate 方式的问题,但除此之外我不知道如何解决这个问题。

【问题讨论】:

  • 使用 Promise.setScheduler

标签: node.js sinon bluebird


【解决方案1】:

您需要像这样手动步进假计时器:

describe('failing test', function() {
  it('test', function(done) {
    Promise.delay(1000).then(function(){
        done(); //This never gets called     
    });
    //
    // ADVANCE THE CLOCK:
    //
    this.clock.tick(1000);
  });
});

顺便说一句,mocha 内置了对 Promise 的支持,因此更好的方法是 return your Promise:

describe('failing test', function() {
  it('test', function() { // No more "done" callback
    var p = Promise.delay(1000).then(function(){
        console.log('done'); 
    });
    this.clock.tick(1000);
    return p; // Return the promise instead - mocha will take of the async magic goodness!
  });
});

根据我的经验,混合 Promise 和 done 回调样式会导致各种麻烦并且难以跟踪错误。使用 Promise 时,尽量坚持返回,并查看诸如 chai-as-promised 之类的库。我向你保证,它会让你的测试更具可读性!

【讨论】:

  • 谢谢!但是,我想避免踩计时器,因为我的测试不需要这样做。由于两个库之间不兼容,我只需要这样做,这听起来不对! PS:感谢您对如何使用 Mocha 处理承诺的建议。
  • 如果你想使用一个假定时器并且也做 Promise.delay(),你别无选择,只能手动步进定时器。实际上,这就是假计时器的全部意义所在 - 让您控制时间的进程。
  • 我同意。在这个测试用例中,虽然我想做的只是冻结时间而不是控制它的进程。这就是为什么踩计时器感觉有点过头了。在将这些库更新到最新版本之前,我想要做的是可能的,这就是为什么我想知道它是否仍然可能。感谢您的建议。
  • 第一个解决方案有效的事实是实现细节,我们将来可能会在 bluebird 中打破它。由于计时器已经是异步的,因此不以延迟方式运行then 是一种优化。这也会导致竞争条件。第二个(承诺)替代方案有效。
【解决方案2】:

最佳做法:

不要使用假计时器,它们会导致奇怪的并发问题,因为它们会同步调用延迟函数——从而改变执行。而是使用 Mocha 的内置承诺支持:

describe('failing test', function() {
    it('test', function(){ 
        return Promise.delay(1000); // return the promise here, no `done`
    });

但如果必须的话

请不要这样做,但您可以告诉 bluebird 同步运行其 then 回调,我强烈建议您不要这样做,它导致您的应用出现计时问题:

Promise.setScheduler(function(fn){
    return fn();
});

【讨论】:

  • 我相信你的建议是完全错误的。首先,实时测试具有完全相同的时间问题(因为您无法真正预测代码在生产机器上执行的速度),其次 - 使测试变得非常非常慢。我同意,由于sinon 计时器(当前为lolex)是同步的,它们确实不是最好的实现。尽管如此,您仍然可以使用异步库(例如 node.js 的 zurvan - 免责声明:我是它的作者)。其次,更改调度程序不会改变任何内容,因为默认使用setImmediate,这与时间无关。
  • 如果您担心所有测试运行缓慢,您可以并行运行所有测试,如果您正确编写代码,通常可以获得超过 10K 的并发测试。至于计时问题,它不会消除现有的计时问题,但不会导致任何新的问题——使用同步假计时器肯定会导致计时问题(这是一个巨大的问题,jQuery 正在重写他们承诺 3.0 在此过程中打破一半的互联网)-有效地引入了竞争条件。仅仅为了测试而引入竞态条件是不好的。
  • 另外,Promise.setScheduler 实际上会在不调用 setImmediate 的情况下运行回调,如果您发现它有任何不同,请提交错误报告以便我修复它 - 绝对不是我们的意思是setScheduler 在 bluebird 中的行为,如果您将调度程序设置为同步运行回调,它将这样做..
  • 据我所知,setScheduler 只是使用传递函数作为 - notabene - 在承诺解决/拒绝后调度任务的一种方式。默认为setImmediate/process.nextTick/setTimeout(0)/MutationObserver,但您当然可以将其更改为您想要的任何内容,包括忽略函数。
  • @TomaszLewowski 是的,您也可以将其更改为同步解决 - 承诺在转弯后运行所有 then 回调(使用蹦床,但实际上您可以像 setImmediate 一样思考它,本机承诺在所有nextTicks 之后和任何setImmediates 之前执行此操作,bluebird 最有可能使用 setImmediate)。承诺的这种行为可以防止竞争条件(一个函数有时是同步的,有时是异步的)。使用 setScheduler 只需调用一个函数即可更改违反 Promises/A+,但也可以编写简单的同步承诺测试。
猜你喜欢
  • 2017-08-20
  • 2021-09-21
  • 1970-01-01
  • 2015-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-13
  • 2014-11-06
相关资源
最近更新 更多