Pertanyaan AngularJS: Mencegah kesalahan $ digest sudah dalam proses ketika memanggil $ scope. $ Apply ()


Saya menemukan bahwa saya perlu memperbarui halaman saya ke ruang lingkup saya secara manual lebih dan lebih sejak membangun aplikasi dalam sudut.

Satu-satunya cara yang saya tahu untuk melakukan ini adalah menelepon $apply() dari lingkup pengendali dan arahan saya. Masalah dengan ini adalah bahwa itu terus melemparkan kesalahan ke konsol yang berbunyi:

Kesalahan: $ digest sudah dalam proses

Adakah yang tahu cara menghindari kesalahan ini atau mencapai hal yang sama tetapi dengan cara yang berbeda?


784
2017-10-04 14:07


asal


Jawaban:


Jangan gunakan pola ini - Ini akan menyebabkan lebih banyak kesalahan daripada yang dipecahkan. Meskipun Anda pikir itu memperbaiki sesuatu, ternyata tidak.

Anda dapat memeriksa apakah a $digest sedang berlangsung dengan memeriksa $scope.$$phase.

if(!$scope.$$phase) {
  //$digest or $apply
}

$scope.$$phase akan kembali "$digest" atau "$apply" jika sebuah $digest atau $apply sedang dalam proses. Saya percaya perbedaan antara negara-negara ini adalah itu $digest akan memproses jam tangan lingkup saat ini dan anak-anaknya, dan $apply akan memproses pengamat dari semua cakupan.

Untuk poin @ dnc253, jika Anda menemukan diri Anda menelepon $digest atau $apply sering, Anda mungkin salah melakukannya. Saya biasanya menemukan saya perlu mencerna ketika saya perlu memperbarui keadaan ruang lingkup sebagai akibat dari peristiwa DOM yang menembak di luar jangkauan Angular. Sebagai contoh, ketika modal bootstrap twitter menjadi tersembunyi. Terkadang peristiwa DOM aktif ketika a $digest sedang berlangsung, terkadang tidak. Itu sebabnya saya menggunakan cek ini.

Saya ingin tahu cara yang lebih baik jika ada yang tahu.


Dari komentar: oleh @anddoutoi

Pola Anti angular.js

  1. Jangan lakukan if (!$scope.$$phase) $scope.$apply(), itu artinya Anda $scope.$apply() tidak cukup tinggi dalam tumpukan panggilan.

635
2017-10-12 12:28



Dari diskusi baru-baru ini dengan orang-orang Angular tentang topik ini: Untuk alasan pemeriksaan masa depan, Anda sebaiknya tidak menggunakannya $$phase

Ketika ditekan untuk cara "benar" untuk melakukannya, jawabannya adalah saat ini

$timeout(function() {
  // anything you want can go here and will safely be run on the next digest.
})

Saya baru-baru ini berlari ke ini ketika menulis layanan sudut untuk membungkus API facebook, google, dan twitter yang, untuk berbagai tingkat, memiliki callback diserahkan.

Berikut ini contoh dari dalam layanan. (Demi keringkasan, sisa layanan - yang mengatur variabel, menyuntikkan $ timeout, dll. - telah ditinggalkan.)

window.gapi.client.load('oauth2', 'v2', function() {
    var request = window.gapi.client.oauth2.userinfo.get();
    request.execute(function(response) {
        // This happens outside of angular land, so wrap it in a timeout 
        // with an implied apply and blammo, we're in action.
        $timeout(function() {
            if(typeof(response['error']) !== 'undefined'){
                // If the google api sent us an error, reject the promise.
                deferred.reject(response);
            }else{
                // Resolve the promise with the whole response if ok.
                deferred.resolve(response);
            }
        });
    });
});

Perhatikan bahwa argumen penundaan untuk $ timeout adalah opsional dan akan ditetapkan ke 0 jika dibiarkan tidak disetel ($ timeout panggilan $ browser.defer yang default ke 0 jika penundaan tidak diatur)

Sedikit tidak intuitif, tapi itulah jawaban dari orang-orang yang menulis Angular, jadi itu cukup baik untuk saya!


631
2017-09-25 04:06



Siklus digest adalah panggilan sinkron. Ini tidak akan menghasilkan kontrol ke loop acara browser sampai selesai. Ada beberapa cara untuk mengatasi hal ini. Cara termudah untuk menghadapinya adalah menggunakan built in $ timeout, dan cara kedua adalah jika Anda menggunakan underscore atau lodash (dan Anda seharusnya), panggil yang berikut:

$timeout(function(){
    //any code in here will automatically have an apply run afterwards
});

atau jika Anda memiliki garis bawah:

_.defer(function(){$scope.$apply();});

Kami mencoba beberapa solusi, dan kami benci menyuntikkan $ rootScope ke semua pengontrol, arahan, dan bahkan beberapa pabrik kami. Jadi, batas waktu $ dan _.defer telah menjadi favorit kami sejauh ini. Metode-metode ini berhasil memberitahu sudut untuk menunggu sampai loop animasi berikutnya, yang akan menjamin bahwa ruang lingkup saat ini. $ Apply selesai.


312
2017-07-30 22:51



Banyak jawaban di sini mengandung nasihat yang baik tetapi juga dapat menyebabkan kebingungan. Cukup menggunakan $timeout aku s tidak yang terbaik atau solusi yang tepat. Juga, pastikan untuk membaca bahwa jika Anda prihatin dengan pertunjukan atau skalabilitas.

Hal-hal yang harus Anda ketahui

  • $$phase bersifat pribadi untuk kerangka kerja dan ada alasan bagus untuk itu.

  • $timeout(callback) akan menunggu hingga siklus digest saat ini (jika ada) selesai, lalu jalankan callback, lalu jalankan di akhir penuh $apply.

  • $timeout(callback, delay, false) akan melakukan hal yang sama (dengan penundaan opsional sebelum menjalankan panggilan balik), tetapi tidak akan mengaktifkan $apply (argumen ketiga) yang menyimpan kinerja jika Anda tidak memodifikasi model Sudut Anda ($ scope).

  • $scope.$apply(callback) meminta, antara lain, $rootScope.$digest, yang berarti akan mengurangi cakupan root aplikasi dan semua anak-anaknya, bahkan jika Anda berada dalam ruang lingkup yang terisolasi.

  • $scope.$digest() hanya akan menyinkronkan modelnya ke tampilan, tetapi tidak akan mencerna ruang lingkup orang tuanya, yang dapat menyimpan banyak pertunjukan ketika bekerja pada bagian yang terisolasi dari HTML Anda dengan ruang lingkup yang terisolasi (dari arahan kebanyakan). $ digest tidak mengambil callback: Anda mengeksekusi kode, lalu mencerna.

  • $scope.$evalAsync(callback) telah diperkenalkan dengan angularjs 1.2, dan mungkin akan menyelesaikan sebagian besar masalah Anda. Silakan lihat paragraf terakhir untuk mempelajarinya lebih lanjut.

  • jika kamu mendapatkan $digest already in progress error, maka arsitektur Anda salah: Anda tidak perlu mengecilkan cakupan Anda, atau Anda seharusnya tidak bertanggung jawab atas hal itu (Lihat di bawah).

Bagaimana menyusun kode Anda

Ketika Anda mendapatkan kesalahan itu, Anda mencoba mencerna ruang lingkup Anda sementara itu sedang berlangsung: karena Anda tidak tahu keadaan lingkup Anda pada saat itu, Anda tidak bertanggung jawab untuk menangani pencernaannya.

function editModel() {
  $scope.someVar = someVal;
  /* Do not apply your scope here since we don't know if that
     function is called synchronously from Angular or from an
     asynchronous code */
}

// Processed by Angular, for instance called by a ng-click directive
$scope.applyModelSynchronously = function() {
  // No need to digest
  editModel();
}

// Any kind of asynchronous code, for instance a server request
callServer(function() {
  /* That code is not watched nor digested by Angular, thus we
     can safely $apply it */
  $scope.$apply(editModel);
});

Dan jika Anda tahu apa yang Anda lakukan dan mengerjakan petunjuk kecil yang terisolasi sementara bagian dari aplikasi Angular besar, Anda dapat memilih $ digest sebagai ganti $ berlaku untuk menyimpan pertunjukan.

Perbarui sejak Angularjs 1.2

Metode baru yang kuat telah ditambahkan ke $ scope apa pun: $evalAsync. Pada dasarnya, ia akan menjalankan callback-nya dalam siklus intisari saat ini jika ada yang terjadi, jika tidak siklus digest baru akan mulai mengeksekusi callback.

Itu masih belum sebagus $scope.$digest jika Anda benar-benar tahu bahwa Anda hanya perlu menyinkronkan bagian yang terisolasi dari HTML Anda (sejak yang baru $apply akan dipicu jika tidak ada yang sedang berlangsung), tetapi ini adalah solusi terbaik ketika Anda menjalankan fungsi yang Anda tidak dapat mengetahuinya jika akan dilaksanakan secara bersamaan atau tidak, misalnya setelah mengambil sumber daya yang berpotensi di-cache: terkadang ini akan memerlukan panggilan async ke server, jika tidak, sumber daya akan diambil secara lokal secara bersamaan.

Dalam kasus ini dan semua yang lain di mana Anda punya !$scope.$$phase, pastikan untuk menggunakannya $scope.$evalAsync( callback )


257
2018-04-16 06:59



Metode pembantu kecil yang berguna untuk menjaga proses ini KERING:

function safeApply(scope, fn) {
    (scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}

86
2018-06-14 18:14



Lihat http://docs.angularjs.org/error/$rootScope:inprog

Masalah muncul ketika Anda memiliki panggilan ke $apply yang kadang-kadang berjalan secara asinkron di luar kode Angular (ketika $ berlaku harus digunakan) dan kadang-kadang serentak di dalam kode Sudut (yang menyebabkan $digest already in progresskesalahan).

Ini dapat terjadi, misalnya, ketika Anda memiliki pustaka yang secara asinkron mengambil item dari server dan menyimpannya. Pertama kali sebuah item diminta, itu akan diambil secara asynchronous agar tidak memblokir eksekusi kode. Namun, untuk kedua kalinya, item tersebut sudah dalam cache sehingga dapat diambil secara bersamaan.

Cara mencegah kesalahan ini adalah memastikan bahwa kode yang dipanggil $apply dijalankan secara asynchronous. Ini dapat dilakukan dengan menjalankan kode Anda di dalam panggilan ke $timeout dengan penundaan diatur ke 0 (yang merupakan default). Namun, panggil kode Anda di dalam $timeout menghilangkan keharusan untuk menelepon $apply, karena $ timeout akan memicu yang lain $digest siklus sendiri, yang akan, pada gilirannya, melakukan semua pembaruan yang diperlukan, dll.

Larutan

Singkatnya, daripada melakukan ini:

... your controller code...

$http.get('some/url', function(data){
    $scope.$apply(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

melakukan hal ini:

... your controller code...

$http.get('some/url', function(data){
    $timeout(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

Telepon saja $apply ketika Anda tahu kode yang menjalankannya akan selalu dijalankan di luar kode Angular (misalnya panggilan Anda ke $ apply akan terjadi di dalam callback yang dipanggil dengan kode di luar kode Angular Anda).

Kecuali seseorang menyadari beberapa kerugian yang berdampak pada penggunaan $timeout lebih $applySaya tidak mengerti mengapa Anda tidak bisa selalu menggunakannya $timeout (dengan nol delay), bukan $apply, karena akan melakukan hal yang kurang lebih sama.


31
2018-01-21 21:33



Saya memiliki masalah yang sama dengan skrip pihak ketiga seperti CodeMirror misalnya dan Krpano, dan bahkan menggunakan metode safeApply yang disebutkan di sini belum memecahkan kesalahan untuk saya.

Tapi apa yang sudah dipecahkan itu adalah menggunakan layanan $ timeout (jangan lupa untuk menyuntikkannya terlebih dahulu).

Jadi, sesuatu seperti:

$timeout(function() {
  // run my code safely here
})

dan jika di dalam kode Anda yang Anda gunakan

ini

mungkin karena ada di dalam pengontrol direktif pabrik atau hanya perlu semacam pengikatan, maka Anda akan melakukan sesuatu seperti:

.factory('myClass', [
  '$timeout',
  function($timeout) {

    var myClass = function() {};

    myClass.prototype.surprise = function() {
      // Do something suprising! :D
    };

    myClass.prototype.beAmazing = function() {
      // Here 'this' referes to the current instance of myClass

      $timeout(angular.bind(this, function() {
          // Run my code safely here and this is not undefined but
          // the same as outside of this anonymous function
          this.surprise();
       }));
    }

    return new myClass();

  }]
)

31
2017-09-03 00:15