【问题标题】:How do you use promises in AngularJS Views (ng-show)?你如何在 AngularJS 视图(ng-show)中使用 Promise?
【发布时间】:2015-04-04 10:22:54
【问题描述】:

我正在尝试从 ng-repeat 输入中获取 $http 调用的响应。然后确定是否显示给定每个响应的单个 ng-repeat。目前实际的 $http api 不可用,所以我只是硬编码了响应。

不使用 Promise 就可以正常工作。但是在使用 promise 方法时,我什至无法在 jsfiddle 上运行它。 我怎样才能让承诺发挥作用?

编辑:感谢 Benjamin Gruenbaum,我修改了我的问题 我实际上并没有使用 $http 而是使用 pouchDB (本地数据库,因此无需担心 DOS)。仅以 $http 为例,因为它也返回了一个承诺。 我真正需要解决的是如何在添加新名称时再次更新承诺。例如,如果添加了“joe”,那么他应该在 'billsNotPaid'

http://jsfiddle.net/TheSlamminSalmon/cs0gxxxd/6/

查看:

<div ng-app>    
    <div ng-controller="MainController">        
        <h1>Hello Plunker!</h1>
        <form ng-submit="AddNewGuy()">
            <input type="text" ng-model="newGuy" placeholder="Insert New Guy"></input>
            <input type="submit"></input>
        </form>

        <strong>Everyone</strong>
        <div ng-repeat="name in names">
            {{name}}
        </div>

        <strong>These guys haven't paid their bills (Non Promise)</strong>
        <div ng-repeat="name in names">
            <div ng-show="NotPaidBillsNonPromise(name)">
                {{name}}
            </div>
        </div> 

        <strong>These guys haven't paid their bills (Using http Promise)</strong>
        <div ng-repeat="name in billsNotPaid">
                {{name}}
        </div>
    </div>
</div>

AngularJS 控制器:

function MainController($scope, $http) {
    $scope.names = [
        "James",
        "Tim",
        "Alex",
        "Sam",
        "Kim"
    ];
    $scope.billsNotPaid = []; // start as empty

    $scope.NotPaidBillsNonPromise = function (name) {
        if (name == "Tim" || name == "Sam" || name == "Joe") return true;
    };

    $scope.NotPaidBills = function (name) {
        return $http.get("http://echo.jsontest.com/name/" + name)
        .then(function (r) {
                return (r.data.name === "Tim" || r.data.name === "Sam" || r.data.name === "Joe")
        });
    };

    $scope.newGuy;
    $scope.AddNewGuy = function () {
        $scope.names.push($scope.newGuy);
    };

    // start the check for each name
    $scope.names.forEach(function(name){ 
        return $scope.NotPaidBills(name).then(function(notPaid){
            console.log(name, notPaid);
            if(notPaid) $scope.billsNotPaid.push(name); 
        });
    });
}

【问题讨论】:

  • NotPaidBills 函数返回undefined
  • 作为一般说明 - 我强烈建议您避免对 n 个输入发出 n 个请求 - 网络流量很昂贵。
  • 正如 Benjamin 所指出的,您在这里所做的确实很糟糕,但比他所说的还要糟糕...您的实现在这里,将为 N 个名称,每个摘要周期发出 N 个服务器请求, 每次 Angular 运行一个... 有大量客户端在运行,而您只是对自己实施了 DoS 攻击 o.O... (作为旁注,ng-if 通常比 ng-show/ng- 更可取隐藏)

标签: javascript angularjs promise angular-promise


【解决方案1】:

首先,我强烈建议您避免在 ng-repeat 中为每个项目发出 HTTP 请求。它很慢,并且可能会导致糟糕的用户体验 - 相反,我建议您将这些请求批处理为一个请求,该请求接受多个值并返回多个值。


Angular 通过两种方式数据绑定执行更新,绑定值通过一个称为摘要循环的循环进行更新 - 因此,每当 Angular 运行这样一个循环时,您的所有值都保证在循环运行时是最新的。

幸运的是 - 因为 $http 返回一个承诺 - Angular 将在调用结束后安排一个摘要,因为承诺通过 $evalAsync 运行它们的 then 回调,这意味着如果一个摘要尚未安排或进行中。

因此,您只需从 $http 承诺履行处理程序更新范围,它就会起作用。我们添加一个新的范围属性:

$scope.billsNotPaid = [];

这是对应的 Angular:

<div ng-repeat="name in billsNotPaid">
    {{name}}
</div>

还有电话:

$scope.NotPaidBills = function (name) {
    return $http.get("http://echo.jsontest.com/name/" + name)
    .then(function (r) {
            return (r.data.name === "Tim" || r.data.name === "Sam")
    });
};

// start the check for each name
$scope.names.forEach(function(name){ 
    return $scope.NotPaidBills(name).then(function(notPaid){
        console.log(name, notPaid);
        if(notPaid) $scope.billsNotPaid.push(name); 
    });
});

Fiddle

【讨论】:

  • 感谢您提供此信息。实际上我不会使用 $http 来获得实际结果。我实际上使用了 pouchDB,它也返回了 Promise。只需使用 $http 作为解决方法来理解 Promise。我已经更新了我的问题,因为我需要动态的“名称”列表。可以在上面添加东西。所以最初的 foreach 循环只会执行一次。
  • @JimmyTo 在这种情况下,您需要确保返回的承诺确实安排了摘要 - 大多数承诺代码作为来自外部角度的代码不会为您执行 - 您可以使用$q.when 并且只有 .then 才能将其从通用承诺(thenable)转换为受信任的 Angular 承诺或设置调度程序。或者,您可以为 pouchdb 使用角度包装器,例如 github.com/wspringer/angular-pouchdb,它基本上可以为您做到这一点——据我所知,一个特别是针对您的特定 ng-repeat 问题的临时解决方案
【解决方案2】:

没什么新意,只是总结了其他答案中所说的内容。

  1. 如果您跨越网络边界,请不要制作繁琐的界面,尤其是当它在像 ng-repeat 这样的循环内时。因此,您应该提前加载任何必要的东西来渲染您的视图(即当视图被激活时),并且对于您需要添加到视图中的每个新项目,您可以将其直接添加到添加处理程序中的 UI 并使用服务将其持久化回数据存储区(如果跨越网络边界/API 是基于 Promise 的,这很可能是基于异步 Promise 的调用)。

  2. 根据 Benjamin 的评论更新... 或者在使用 jsfiddle 或类似的东西时替代 $http,您绝对可以使用 angular $q 或 $timeout 服务。简而言之,通过调用 var dfd = $q.defer(); 创建一个 defer 对象,使用 dfd.resolve 解析承诺,最后使用 return dfd.promise 返回承诺或使用 $timeout 包装模拟网络调用并返回结果像这样在超时函数内部:

--

function asyncSomething() {
  var dfd = $q.defer();
  var result = 1 + 1; //do something async
  dfd.resolve(result);
  return dfd.promise;
}

function asyncSomething() {
    return $timeout(function() {
        return 1 + 1; ///do something async
    }, 1000);  //pretend async call that last 1 second...
}
  1. 是的,如果您还没有关注任何人,请使用John Papa's Angular style guide。这只是一种很好的做法。

这里是你的小提琴改成约翰爸爸的风格作为上面提到的例子:http://jsfiddle.net/kx4gvsc0/30/

在上面的示例中,我试图以不同的方式解决问题。我没有循环遍历列表中的所有名称,而是将刷新欠款列表的责任移交给 addCustomer 方法(是的,这不是一个好主意,您可以将其分为 2 个更符合 SRP 的调用,但对于为了使调用变得笨重...),无论如何,作为异步添加客户调用的一部分,它链接另一个承诺(可能是 pouchdb 调用,也是承诺基础),它将刷新欠款列表并返回返回控制器进行绑定。

    function addCustomerAndRefreshOwingList(customerName) {
        return $timeout(function() {                
            //persist the new customer to the data source
            _customers.push({ name: customerName, paid: false});
        }, 250) //pretend network request lasting 1/4 second
        .then(refreshOwingList);  //chain another promise to get the owing list <-- This is your pouch db call perhaps...
    }

    function refreshOwingList() {
        return $timeout(function() {                                
            //Fake logic here to update certain payers per call using the counter
            switch(counter) {
                case 0:  // first pass...
                    _customers[1].paid = true; //Tim paid
                    break;
                case 1:  // second pass...
                    _customers[2].paid = true;  //Alex paid
                    _customers[4].paid = true;  //Kim paid
                    break;
                default:
                    break;
            }

            counter++;

            //return all customers that owes something
            return _customers
                .filter(function(c) { return !c.paid; })
                .map(function(c) { return c.name; });

        }, 500); //pretend this is taking another 1/2 second        
    }

【讨论】:

  • 第 2 点是 the deffered anti-pattern,因为 $timeout 已经返回了一个承诺。第 3 点与问题无关,是基于意见的。第 1 点只是很好的建议。我看不到你是如何解决 OP 的问题的。
  • 啊,没想到...我需要尝试一下,不用延迟。
  • 在阅读您的评论并再次阅读问题后,我已经更正了上面的示例和答案。
猜你喜欢
  • 2012-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-08
  • 2014-03-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多