TypeError: Невозможно вызвать метод 'then' из undefined Angularjs
Я новичок в Angular и имею проблемы с выполнением синхронной операции. Я разрешил несколько вопросов, которые пришли мне в голову с контроллером Angular, где я получаю сообщение об ошибке "Невозможно вызывать метод then of undefined", который вызывается из файла newController.
angular.module('newApp.newController', ['angularSpinner', 'ui.bootstrap'])
.controller('newController', function($q, $scope, utilityFactory, $http) {
utilityFactory.getData().then(function(data) {
console.log("success");
console.log(data);
});
});
angular.module('newApp.utility', [])
.factory('utilityFactory', function($q, $http) {
var utils = {};
//This is a cordova plugin
var getLauncher = function() {
return window.plugin.launcher;
};
var success = function(data) {
console.log(device);
return device;
}
var fail = function(error) {
console.log("error", error);
};
utils.getData = function() {
/* Get the store number details initially before initalizing the application */
if (window.plugin) {
var launcher = getLauncher();
console.log("Fetching data from device");
//Cordova js is returning this method
return launcher.getDevice(success, fail);
}
};
return utils;
})
Ответы
Ответ 1
С пониманием, что:
Launcher.prototype.getDevice = function(successCallback, failureCallback) {
exec(successCallback, failureCallback, KEY, 'getDevice', []);
}
мы знаем, что window.plugin.launcher.getDevice()
возвращает undefined
, а не объект данных. Вместо этого он обеспечивает свой ответ посредством своих обратных вызовов успеха/отказа.
Следовательно, для работы с promises, window.plugin.launcher.getDevice()
должно быть "обещано", включая явное создание new Promise()
и его разрешение/отклонение с помощью обратных вызовов .getDevice. (Просто обертка в $q (...) - это не одно и тоже не работает).
angular.module('newApp.utility', []).factory('utilityFactory', function($q, $http) {
return {
getDevice: function() {
return $q.defer(function(resolve, reject) {
window.plugin.launcher.getDevice(resolve, reject); // If this line throws for whatever reason, it will be automatically caught internally by Promise, and `reject(error)` will be called. Therefore you needn't explicitly fork for cases where `window.plugin` or `window.plugin.launcher` doesn't exist.
}).promise;
}
};
});
Вызов от контроллера должен теперь работать:
angular.module('newApp.newController', ['angularSpinner', 'ui.bootstrap']).controller('newController', function($q, $scope, utilityFactory, $http) {
return utilityFactory.getDevice().then(function(data) {
console.log(data);
}).catch(function(error) {
console.error(error);
});
});
Ответ 2
return launcher.getDevice(success, fail);
эта строка является проблемой, я бы просто обернул ее обещанием:
return $q(launcher.getDevice.bind(launcher, success, fail));
Изменить: также нужно позаботиться о другом состоянии, поэтому код будет выглядеть следующим образом:
utils.getData = function() {
/* Get the store number details initially before initalizing the application */
if (window.plugin) {
var launcher = getLauncher();
console.log("Fetching data from device");
//Cordova js is returning this method
return $q(launcher.getDevice.bind(launcher, success, fail));
}
return $q.resolve(); // or $q.reject(reason);
};
Ответ 3
1) Фактический модуль должен быть "newApp", а не "newApp.newController" и "newApp.utility". Это помещает эти два компонента в отдельные модули, а не в модуль myApp.
2) Вы должны использовать только синтаксис
angular.module('newApp', [])
всякий раз, когда вы объявляете новый модуль. Если вы хотите получить доступ к модулю, вы должны использовать
angular.module('newApp')
https://docs.angularjs.org/api/ng/function/angular.module
3) Ваш utilityFactory возвращает переменную 'device', которая не была объявлена нигде
4) Вы не можете использовать "тогда", не возвращая обещание в своей функции getData. Тогда это метод, который реализован в Javascript promises, поэтому вы не можете просто использовать его в любом месте своего кода.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
utils.getData = function() {
var deferred = $q.defer();
if (window.plugin) {
var launcher = getLauncher();
console.log("Fetching data from device");
//Cordova js is returning this method
return launcher.getDevice(success, fail);
}
return deferred.promise;
};
Вот код, который я использовал при отладке вашего кода. Я немного изменил ваш код, но он даст пример функции, работающей при возвращении обещания. http://codepen.io/anon/pen/QNEEyx?editors=1010
Ответ 4
Как уже упоминалось в других ответах, вам нужно вернуть Promise
из вашей функции utils.getData
. Angular $q
Помощник позволяет вам сделать именно это. Однако то, как некоторые другие ответы показывают, что вы это делаете, противоречит лучшим практикам. При использовании $q
наилучшей практикой является следующее:
var myPromise = $q(function (resolve, reject) {
// Do some logic in here that is asynchronous and either invoke resolve with
// the results or reject with an error that may have occurred
});
Следовательно, ваш код будет выглядеть следующим образом:
angular.module('newApp.utility', [])
.factory('utilityFactory', function($q, $http) {
var utils = {};
//This is a cordova plugin
var getLauncher = function() {
return window.plugin.launcher;
};
var success = function(data) {
console.log(device);
return device;
}
var fail = function(error) {
console.log("error", error);
};
utils.getData = function() {
/* Get the store number details initially before initalizing the application */
return $q(function (resolve, reject) {
if (!window.plugin) {
// You can handle this case as a rejection of the promise
reject(new Error('Window plugin not found'));
return;
}
var launcher = getLauncher();
console.log("Fetching data from device");
//Cordova js is returning this method
// When device is ready it will "resolve" the promise and
// invoke any of the ".then()" functions you add to the promise
// If an error occurs, it will invoke any ".catch()" functions
// that you have added.
launcher.getDevice(resolve, reject);
});
};
return utils;
})
Для получения дополнительной информации об услуге $q
, проверьте этот пост из официальной документации AngularJS:
https://docs.angularjs.org/api/ng/service/ $q
Кроме того, некоторые ресурсы, если вы хотите узнать больше о promises и асинхронном программировании в JavaScript:
Активный инструмент визуализации для promises - http://bevacqua.github.io/promisees/#
Учебник по promises - https://www.toptal.com/javascript/javascript-promises
Еще одна вещь, которая рассматривается в качестве общего руководства для лучших методов AngularJS, - это руководство по стилю Angular от John Papa: https://github.com/johnpapa/angular-styleguide
Наконец, способ настройки вашего модуля немного отключен. Каждый вызов angular.module(moduleName, dependencies)
создаст новый модуль с этими зависимостями. Хотя рекомендуется разложить ваше приложение Angular на несколько модулей, вам необходимо убедиться, что ваше корневое или основное приложение, на которое ссылаются с помощью директивы ng-app
, имеет ссылки на все ваши вспомогательные модули и что любые модуль, который ссылается на зависимости от другого модуля, имеет этот модуль, включенный в его список зависимостей.
В вашем случае вы создаете модуль с именем newApp.newController
, но по своему усмотрению он не будет работать, потому что он пытается ссылаться на utilityFactory
, который определен в отдельном модуле под названием newApp.utility
, но не является на который ссылается ваш модуль newApp.newController
. Чтобы исправить это, сделайте следующее:
angular.module('newApp.newController', ['angularSpinner', 'ui.bootstrap', 'newApp.utility'])
// Make sure to add 'newApp.utility' to the dependencies of the 'newApp.newController' module.
В качестве альтернативы вы можете просто создать как контроллер, так и утилиту factory в том же модуле:
// Create the module once
angular.module('newApp', ['angularSpinner', 'ui.bootstrap']);
// Reference it by invoking it with just one parameter
angular.module('newApp').controller('newController', ...);
angular.module('newApp').factory('utilityFactory', ...);
Использование и рекомендации по модулям Angular можно найти здесь:
https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#modules