【问题标题】:TypeError: Cannot call method 'then' of undefined AngularjsTypeError:无法调用未定义 Angularjs 的方法“then”
【发布时间】:2016-06-14 18:09:29
【问题描述】:

我对 Angular 很陌生,并且在进行同步操作时遇到了问题。我已经解决了 Angular 控制器遇到的一些问题,我从 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;
    })

【问题讨论】:

  • getData 没有返回任何东西
  • 我也用过这个 return launcher.getDevice(success, fail);
  • 但是else 不会返回任何东西,launcher.getDevice 会返回什么?
  • Launcher.prototype.getDevice = function(successCallback, failureCallback) { exec(successCallback, failureCallback, KEY, 'getDevice', []); };
  • 'launcher.getDevice' 从 android 手机如 macId 返回设备相关信息

标签: javascript angularjs promise


【解决方案1】:

正如提到的其他一些答案,您需要从您的utils.getData 函数返回一个Promise。 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

此外,如果您想了解更多关于 JavaScript 中的 Promise 和异步编程的信息,请参考以下资源:

简洁的 Promises 可视化工具 - http://bevacqua.github.io/promisees/#

promise 教程 - https://www.toptal.com/javascript/javascript-promises

另一个值得研究的 AngularJS 最佳实践的一般指南是 John Papa 的 Angular 风格指南: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.

或者,您可以在同一个模块中同时创建控制器和实用程序工厂:

// 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

【讨论】:

    【解决方案2】:

    理解为:

    Launcher.prototype.getDevice = function(successCallback, failureCallback) {
        exec(successCallback, failureCallback, KEY, 'getDevice', []);
    }
    

    ,我们知道window.plugin.launcher.getDevice() 返回undefined,而不是数据对象。相反,它通过成功/失败回调提供响应。

    因此,要使用 Promise,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);
        });
    });
    

    【讨论】:

    • 通过传递Promise的resolvereject函数作为window.plugin.launcher.getDevice()的successCallback和errorCallback。
    • 'ReferenceError: Promise is not defined' 得到这个错误
    • 哎呀,我假设 ES6 承诺。尝试使用 $q 承诺进行相同的操作。即return $q.defer(function(resolve, reject) {...}).promise;
    • 这样它就不起作用,也不会抛出任何错误。但只是响应没有达到控制器的范围
    • exec(successCallback, failureCallback, KEY, 'getDevice', []); 命令是否记录在某处?
    【解决方案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) 您的实用程序工厂正在返回一个尚未在任何地方声明的变量“设备”

    4) 如果没有在 getData 函数中返回承诺,就不能使用 'then'。 Then 是一种在 Javascript 承诺中实现的方法,因此您不能只在代码中的任何地方使用它。 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】:
          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);
          };
      

      【讨论】:

      • 我会检查一下。
      • 试过这个,调用getDevice方法后进入错误回调函数并打印消息-“错误”
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-28
      • 2015-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多