获取“$digest 已经在进行中"在 Jasmine 2.0 的异步测试中 [英] Getting "$digest already in progress" in async test with Jasmine 2.0

查看:12
本文介绍了获取“$digest 已经在进行中"在 Jasmine 2.0 的异步测试中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道在摘要循环期间手动调用 $digest$apply 会导致$digest already in progress"错误,但我不知道为什么会这样在这里得到它.

I know that calling $digest or $apply manually during a digest cycle will cause a "$digest already in progress" error but I have no idea why I am getting it here.

这是对包装 $http 的服务的单元测试,该服务很简单,它只是防止对服务器进行重复调用,同时确保尝试进行调用的代码仍然可以获取它预期的数据.

This is a unit test for a service that wraps $http, the service is simple enough, it just prevents making duplicate calls to the server while ensuring that code that attempts to do the calls still gets the data it expected.

angular.module('services')
    .factory('httpService', ['$http', function($http) {

        var pendingCalls = {};

        var createKey = function(url, data, method) {
            return method + url + JSON.stringify(data);
        };

        var send = function(url, data, method) {
            var key = createKey(url, data, method);
            if (pendingCalls[key]) {
                return pendingCalls[key];
            }
            var promise = $http({
                method: method,
                url: url,
                data: data
            });
            pendingCalls[key] = promise;
            promise.then(function() {
                delete pendingCalls[key];
            });
            return promise;
        };

        return {
            post: function(url, data) {
                return send(url, data, 'POST');
            },
            get: function(url, data) {
                return send(url, data, 'GET');
            },
            _delete: function(url, data) {
                return send(url, data, 'DELETE');
            }
        };
    }]);

单元测试也很简单,它使用 $httpBackend 来期待请求.

The unit-test is also pretty straight forward, it uses $httpBackend to expect the request.

it('does GET requests', function(done) {
    $httpBackend.expectGET('/some/random/url').respond('The response');

    service.get('/some/random/url').then(function(result) {
        expect(result.data).toEqual('The response');
        done();
    });
    $httpBackend.flush();
});

done() 被调用并出现$digest already in progress"错误时,这会爆炸.我不知道为什么.我可以通过在这样的超时中包装 done() 来解决这个问题

This blows up as sone as done() gets called with a "$digest already in progress" error. I've no idea why. I can solve this by wrapping done() in a timeout like this

setTimeout(function() { done() }, 1);

这意味着 done() 将在 $digest 完成后排队并运行,但是虽然这解决了我的问题,但我想知道

That means done() will get queued up and run after the $digest is done but while that solves my problem I want to know

  • 为什么 Angular 一开始就处于摘要循环中?
  • 为什么调用 done() 会触发这个错误?
  • Why is Angular in a digest-cycle in the first place?
  • Why does calling done() trigger this error?

我在 Jasmine 1.3 上运行了完全相同的测试,这只是在我升级到 Jasmine 2.0 并重写测试以使用新的 async-syntax 之后发生的.

I had the exact same test running green with Jasmine 1.3, this only happened after I upgraded to Jasmine 2.0 and rewrote the test to use the new async-syntax.

推荐答案

$httpBacked.flush() 实际上开始并完成了一个 $digest() 循环.昨天我花了一整天的时间深入研究 ngResource 和 angular-mocks 的源代码以深入了解它,但仍然没有完全理解它.

$httpBacked.flush() actually starts and completes a $digest() cycle. I spent all day yesterday digging into the source of ngResource and angular-mocks to get to the bottom of this, and still don't fully understand it.

据我所知,$httpBackend.flush() 的目的是完全避免上面的异步结构.换句话说,it('should do something',function(done){});$httpBackend.flush() 的语法不能很好地结合使用..flush() 的真正目的是推动待处理的异步回调,然后返回.它就像一个大的 done 包装器,围绕着你的所有异步回调.

As far as I can tell, the purpose of $httpBackend.flush() is to avoid the async structure above entirely. In other words, the syntax of it('should do something',function(done){}); and $httpBackend.flush() do not play nicely together. The very purpose of .flush() is to push through the pending async callbacks and then return. It is like one big done wrapper around all of your async callbacks.

所以如果我理解正确(现在它对我有效),正确的方法是在使用 $httpBackend.flush() 时删除 done() 处理器:

So if I understood correctly (and it works for me now) the correct method would be to remove the done() processor when using $httpBackend.flush():

it('does GET requests', function() {
    $httpBackend.expectGET('/some/random/url').respond('The response');

    service.get('/some/random/url').then(function(result) {
        expect(result.data).toEqual('The response');
    });
    $httpBackend.flush();
});

如果你添加console.log语句,你会发现所有的回调都在flush()循环中持续发生:

If you add console.log statements, you will find that all of the callbacks consistently happen during the flush() cycle:

it('does GET requests', function() {
    $httpBackend.expectGET('/some/random/url').respond('The response');

    console.log("pre-get");
    service.get('/some/random/url').then(function(result) {
        console.log("async callback begin");
        expect(result.data).toEqual('The response');
        console.log("async callback end");
    });
    console.log("pre-flush");
    $httpBackend.flush();
    console.log("post-flush");
});

那么输出将是:

预购

预冲洗

异步回调开始

异步回调结束

冲洗后

每次.如果你真的想看,抓住范围,看看 scope.$$phase

Every time. If you really want to see it, grab the scope and look at scope.$$phase

var scope;
beforeEach(function(){
    inject(function($rootScope){
        scope = $rootScope;
    });
});
it('does GET requests', function() {
    $httpBackend.expectGET('/some/random/url').respond('The response');

    console.log("pre-get "+scope.$$phase);
    service.get('/some/random/url').then(function(result) {
        console.log("async callback begin "+scope.$$phase);
        expect(result.data).toEqual('The response');
        console.log("async callback end "+scope.$$phase);
    });
    console.log("pre-flush "+scope.$$phase);
    $httpBackend.flush();
    console.log("post-flush "+scope.$$phase);
});

你会看到输出:

预先获得未定义

预刷新未定义

异步回调开始 $digest

async callback begin $digest

异步回调结束 $digest

async callback end $digest

刷新后未定义

这篇关于获取“$digest 已经在进行中"在 Jasmine 2.0 的异步测试中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆