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