mirror of
https://github.com/balena-io/etcher.git
synced 2025-11-06 08:58:31 +00:00
This error was reported by TrackJS various times:
```
TypeError: Cannot read property 'length' of undefined
at EventEmitter.<anonymous> (file:///Users/jviotti/Projects/resin/etcher/lib/browser/app.js:104:15)
at emitOne (events.js:77:13)
at EventEmitter.emit (events.js:169:7)
at /Users/jviotti/Projects/resin/etcher/lib/browser/modules/drive-scanner.js:131:17
at processQueue (/Users/jviotti/Projects/resin/etcher/node_modules/angular/angular.js:15616:28)
at /Users/jviotti/Projects/resin/etcher/node_modules/angular/angular.js:15632:27
at Scope.$eval (/Users/jviotti/Projects/resin/etcher/node_modules/angular/angular.js:16884:28)
at Scope.$digest (/Users/jviotti/Projects/resin/etcher/node_modules/angular/angular.js:16700:31)
at /Users/jviotti/Projects/resin/etcher/node_modules/angular/angular.js:16923:26
at completeOutstandingRequest (/Users/jviotti/Projects/resin/etcher/node_modules/angular/angular.js:5825:10),
```
The error refers to the following line in `app.js`:
```js
if (drives.length === 1 && self.selection.hasImage()) {
```
Which indicates that the array of detected drives returned to the main
controller is `undefined` for some reason.
The problem resides in the `.scan()` method of `DriveScannerService`:
```js
this.scan = function() {
return $q.when(drives.listRemovable()).catch(dialog.showError);
};
```
When an error is thrown when scanning the drives, the `.catch()` block
is called. This means that the error is not propagated to the outer code
and the promise resolves with `undefined`.
The solution is to move `.catch()` to the place `.scan()` is called,
instead of making use of it in the low-level parts of the process.
164 lines
3.9 KiB
JavaScript
164 lines
3.9 KiB
JavaScript
/*
|
|
* Copyright 2016 Resin.io
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
/**
|
|
* @module Etcher.drive-scanner
|
|
*/
|
|
|
|
const angular = require('angular');
|
|
const _ = require('lodash');
|
|
const EventEmitter = require('events').EventEmitter;
|
|
const electron = require('electron');
|
|
|
|
if (window.mocha) {
|
|
var path = require('path');
|
|
var srcPath = path.join(__dirname, '..', '..', 'src');
|
|
var drives = electron.remote.require(path.join(srcPath, 'drives'));
|
|
var dialog = electron.remote.require(path.join(srcPath, 'dialog'));
|
|
} else {
|
|
var drives = electron.remote.require('./src/drives');
|
|
var dialog = electron.remote.require('./src/dialog');
|
|
}
|
|
|
|
const driveScanner = angular.module('Etcher.drive-scanner', []);
|
|
|
|
driveScanner.service('DriveScannerService', function($q, $interval, $timeout) {
|
|
let self = this;
|
|
let interval = null;
|
|
|
|
/**
|
|
* @summary List of available drives
|
|
* @type {Object[]}
|
|
* @public
|
|
*/
|
|
this.drives = [];
|
|
|
|
/**
|
|
* @summary Check if there are available drives
|
|
* @function
|
|
* @public
|
|
*
|
|
* @returns {Boolean} whether there are available drives
|
|
*
|
|
* @example
|
|
* if (DriveScannerService.hasAvailableDrives()) {
|
|
* console.log('There are available drives!');
|
|
* }
|
|
*/
|
|
this.hasAvailableDrives = function() {
|
|
return !_.isEmpty(self.drives);
|
|
};
|
|
|
|
/**
|
|
* @summary Set the list of drives
|
|
* @function
|
|
* @public
|
|
*
|
|
* @param {Object[]} drives - drives
|
|
*
|
|
* @example
|
|
* DriveScannerService.scan().then(function(drives) {
|
|
* DriveScannerService.setDrives(drives);
|
|
* });
|
|
*/
|
|
this.setDrives = function(drives) {
|
|
|
|
// Only update if something has changed
|
|
// to avoid unnecessary DOM manipulations
|
|
// angular.equals ignores $$hashKey by default
|
|
if (!angular.equals(self.drives, drives)) {
|
|
self.drives = drives;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @summary Get available drives
|
|
* @function
|
|
* @public
|
|
*
|
|
* @fulfil {Object[]} - drives
|
|
* @returns {Promise}
|
|
*
|
|
* @example
|
|
* DriveScannerService.scan().then(function(drives) {
|
|
* console.log(drives);
|
|
* });
|
|
*/
|
|
this.scan = function() {
|
|
return $q.when(drives.listRemovable());
|
|
};
|
|
|
|
/**
|
|
* @summary Scan drives and populate `.drives`
|
|
* @function
|
|
* @public
|
|
*
|
|
* @description
|
|
* This function returns an event emitter instance
|
|
* that emits a `scan` event everything it scans
|
|
* the drives successfully.
|
|
*
|
|
* @param {Number} ms - interval milliseconds
|
|
* @returns {EventEmitter} event emitter instance
|
|
*
|
|
* @example
|
|
* const emitter = DriveScannerService.start(2000);
|
|
*
|
|
* emitter.on('scan', function(drives) {
|
|
* console.log(drives);
|
|
* });
|
|
*/
|
|
this.start = function(ms) {
|
|
let emitter = new EventEmitter();
|
|
|
|
const fn = function() {
|
|
return self.scan().then(function(drives) {
|
|
emitter.emit('scan', drives);
|
|
self.setDrives(drives);
|
|
}).catch(dialog.showError);
|
|
};
|
|
|
|
// Make sure any pending interval is cancelled
|
|
// to avoid potential memory leaks.
|
|
self.stop();
|
|
|
|
// Call fn after in the next process tick
|
|
// to be able to capture the first run
|
|
// in unit tests.
|
|
$timeout(function() {
|
|
fn();
|
|
interval = $interval(fn, ms);
|
|
});
|
|
|
|
return emitter;
|
|
};
|
|
|
|
/**
|
|
* @summary Stop scanning drives
|
|
* @function
|
|
* @public
|
|
*
|
|
* @example
|
|
* DriveScannerService.stop();
|
|
*/
|
|
this.stop = function() {
|
|
$interval.cancel(interval);
|
|
};
|
|
|
|
});
|