Ошибка “$apply already in progress” в событии FileReader.onloadstart

238
10 октября 2017, 06:11

Столкнулся с разным поведением скрипта в браузерах Mozilla Firefox v56.0 и Google Chrome v61.0.

Разница в $digest цикле. В Мозиле возникает ошибка $apply already in progress.

Пример:

var app = angular.module('app', []); 
 
app.controller('downloadCtrl', function($scope) { 
  $scope.stages = []; 
  $scope.download = function() { 
    let file = new Blob(["Sample text data"]); 
    let fileReader = new FileReader(); 
    fileReader.onloadstart = function() { 
      console.log("onloadstart"); 
      $scope.stages.push('onloadstart'); 
      $scope.$apply(); 
    }; 
    fileReader.onload = function() { 
      console.log("onload"); 
      $scope.stages.push('onload'); 
      $scope.$apply(); 
    }; 
    fileReader.onprogress = function(event) { 
      console.log("onprogress"); 
      $scope.stages.push('onprogress'); 
      $scope.$apply(); 
    }; 
    fileReader.readAsArrayBuffer(file); 
  }; 
}); 
app.directive('download', function($http) { 
  return { 
    restrict: 'EA', 
    template: `<button ng-click="download()"> 
    						Download 
               </button> 
              <div ng-repeat="stage in stages track by $index">{{stage}}</div>`, 
    scope: {}, 
    controller: 'downloadCtrl', 
  }; 
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script> 
<div ng-app="app"> 
  <download></download> 
</div>

Также на jsfiddle.

Почему это происходит?

P.S. Workaround знаю, можно проверить, находимся мы уже в $digest цикле или нет, с помощью $scope.$$phase. Интересует именно, почему так происходит.

Update

Вроде бы событие onloadstart в Мозиле уже идет в $digest цикле. Остальные два события onload и onprogress как и ожидается выполняются вне контекста ангуляра.

Update 2

Добавил печать call stack. Действительно, в Мозиле событие onloadstart происходит синхронно.

var app = angular.module('app', []); 
 
app.controller('downloadCtrl', function($scope) { 
  $scope.stages = []; 
 
  function printStack(note) { 
    console.log((new Error("printStack: " + note)).stack.toString()); 
  } 
  $scope.download = function() { 
    let file = new Blob(["Sample text data"]); 
    let fileReader = new FileReader(); 
    printStack('sync'); 
    fileReader.onloadstart = function() { 
      console.log("onloadstart"); 
      $scope.stages.push('onloadstart'); 
      printStack('async'); 
      //$scope.$apply(); 
    }; 
    fileReader.onload = function() { 
      console.log("onload"); 
      $scope.stages.push('onload'); 
      printStack('async'); 
      $scope.$apply(); 
    }; 
    fileReader.onprogress = function(event) { 
      console.log("onprogress", event); 
      $scope.stages.push('onprogress'); 
      printStack('async'); 
      $scope.$apply(); 
    }; 
    fileReader.readAsArrayBuffer(file); 
  }; 
}); 
app.directive('download', function($http) { 
  return { 
    restrict: 'EA', 
    template: `<button ng-click="download()"> 
    						Download 
               </button> 
              <div ng-repeat="stage in stages track by $index">{{stage}}</div>`, 
    scope: {}, 
    controller: 'downloadCtrl', 
  }; 
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script> 
<div ng-app="app"> 
  <download></download> 
</div>

READ ALSO
js который экспортирует таблицу в excel

js который экспортирует таблицу в excel

Нашел на просторах интернета данный скрипт:

224
Скрыть блок при свайпе/скролле

Скрыть блок при свайпе/скролле

Ниже приведенный код скрывает блок по клику или тапу вне блокаКак скрыть блок при прокрутке/свайпе?

217
Как сделать скриншот содержимого webview вместе с прокруткой в Electron?

Как сделать скриншот содержимого webview вместе с прокруткой в Electron?

Я использовал метод webviewcapturePage, но он возвращает только скриншот видимой части

299
Ya share, twitter не подхватывает картинку

Ya share, twitter не подхватывает картинку

при публикации через ya-share twitter не получает переданную картинку

180