36

$digestダイジェスト サイクル中にまたはを手動で呼び出す$applyと、「$digest は既に進行中です」というエラーが発生することはわかっていますが、なぜここでエラーが発生するのかわかりません。

これは をラップするサービスの単体テストです。この$httpサービスは十分に単純で、サーバーへの重複呼び出しを防止するだけでなく、呼び出しを実行しようとするコードが期待どおりのデータを取得できるようにします。

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、リクエストを期待するために使用します。

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()このようなタイムアウトでラップすることでこれを解決できます

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

つまりdone()、 $digest が完了した後にキューに入れられて実行されますが、それで問題が解決する間、私は知りたいです

  • そもそもAngularがダイジェストサイクルにあるのはなぜですか?
  • done()呼び出しによってこのエラーが発生するのはなぜですか?

Jasmine 1.3 でまったく同じテストをグリーンで実行しました。これは、Jasmine 2.0 にアップグレードし、新しい async-syntax を使用するようにテストを書き直した後にのみ発生しました。

4

3 に答える 3

74

$httpBacked.flush()実際に$digest()サイクルを開始して完了します。私は昨日一日中 ngResource と angular-mocks のソースを掘り下げてこれを理解しましたが、まだ完全には理解していません。

私の知る限り、の目的は$httpBackend.flush()上記の非同期構造を完全に回避することです。言い換えると、 と の構文はうまく連携しませんit('should do something',function(done){});$httpBackend.flush()目的.flush()、保留中の非同期コールバックをプッシュしてから戻ることです。doneこれは、すべての非同期コールバックを包む1 つの大きなラッパーのようなものです。

したがって、私が正しく理解していれば(そして今はうまくいきます)、正しい方法は、done()使用時にプロセッサを削除することです$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();
});

flush()console.log ステートメントを追加すると、サイクル中にすべてのコールバックが一貫して発生することがわかります。

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

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

非同期コールバック終了 $digest

ポストフラッシュ未定義

于 2014-10-08T08:09:04.120 に答える