【问题标题】:Promise chaining and dealing with an action not returning anything down the chain of promisesPromise 链接和处理在 Promise 链中不返回任何内容的操作
【发布时间】:2024-05-20 13:55:01
【问题描述】:

我有一个关于 Javascript 承诺链的问题。假设我在承诺链的某处采取了行动。该操作不会返回任何值,但它必须在链可以继续之前完成。

我需要将该动作包装在一个承诺中吗?我需要类似的东西吗:

$q.when();

请参阅下面的代码:

...
var goToDashboard = function () {
    //TODO: use $q here?
    $state.go('dashboard');
};
...
activateEmail().then(signinByToken).then(setPersonalInfo).then(goToDashboard).then(somethingElse).catch(reportProblem);

有人可以建议吗?

【问题讨论】:

    标签: angularjs angular-promise


    【解决方案1】:

    在下文中,我演示了使用来自各种函数的 Promise 和其他返回类型来链接 .then。当然,如果不返回承诺,则不会延迟解决,并且以下.then 会立即执行-因此,如果您有需要完成的异步任务,则需要返回在异步任务完成时解析的承诺。请注意,返回 $q.when() 确实会返回一个承诺(包含您作为参数提供的任何内容),但它会立即得到解决。

    另外,请注意$state.go actually returns a promise!因此,在您上面的示例中,您可以只执行 return $state.go('dashboard'); 和下面的 .then,直到 ui-router 更改了路由(如下所示)。

    (function() {
      "use strict";
    
      angular.module('myApp', ['ui.router', 'ngResource'])
        .controller('myController', ['$scope', '$state', '$q', '$timeout', '$resource', '$log', MyController])
        .config(['$stateProvider', configUiRouter]);
    
      function configUiRouter($stateProvider) {
        $stateProvider
          .state("home", {
            url: "/home",
            template: "<div>Home state</div>"
          })
          .state("dashboard", {
            url: "/dashboard",
            template: "<div>Dashboard state</div>"
          })
          .state("error", {
            url: "/error",
            template: "<div>Error state: I'm sorry Dave, I'm afraid I can't do that...</div>"
          });
      }
    
      function MyController($scope, $state, $q, $timeout, $resource, $log) {
    
        $scope.status = {
          emailActivated: false,
          signinByToken: false,
          personalInfo: false,
          *Users: null,
          loading: null,
          counter: 0
        };
    
        $state.go('home'); // set default state for ui-router test
    
        activateEmail()
          .then(updateStatusLoading).then(counting) // Loading: . Counter: 1
          .then(signinByToken)
          .then(updateStatusLoading).then(counting) // Loading: .. Counter: 2
          .then(setPersonalInfo)
          .then(updateStatusLoading).then(counting) // Loading: ... Counter: 3
          .then(goToDashboard)
          .then(updateStatusLoading).then(counting) // Loading: .... Counter: 4
          .then(somethingElse)
          .then(triggerError)
          .then(neverReached)
          .catch(catchesReject);
    
    
        /* * * * * * * * * * *
         * Promise functions *
         * * * * * * * * * * */
    
        // doesn't return any promise
        // (resolves immediately)
        function updateStatusLoading() {
          if (!$scope.status.loading) {
            $scope.status.loading = "";
          }
          $scope.status.loading += ".";
        }
    
        // returns something other than a promise (a String...)
        // (resolves immediately)
        function counting() {
          $scope.status.counter++;
          return "Did some counting... (" + $scope.status.counter + ")";
        }
    
        // using promise returned by $timeout
        // (delayed resolution)
        function activateEmail() {
          return $timeout(function simulateActivateEmailLatency() {
            $scope.status.emailActivated = true;
          }, 1000);
        }
    
        // using promise returned by $q.defer, resolved in a $timeout
        // (the return is immediate, but the resolve is delayed)
        function signinByToken() {
          var deferred = $q.defer();
    
          $timeout(function simulateSignInLatency() {
            $scope.status.signinByToken = true;
            deferred.resolve({
              returningSomething: "Is entirely optional"
            });
          }, 1000);
    
          //log to console what this object looks like
          $log.log("deferred.promise: ", deferred.promise);
    
          return deferred.promise;
        }
    
        // using promise created by $q.when; no timeout
        // (immediate resolution)
        function setPersonalInfo() {
          $scope.status.personalInfo = true;
    
          $log.log("$q.when: ", $q.when({
            foo: "bar"
          }));
    
          return $q.when({
            returningSomething: "Is entirely optional"
          });
        }
    
        // using promise created by $state.go
        // (will resolve once route has changed; which could include time spent doing ui-router resolves...)
        function goToDashboard() {
          // yup, this returns a promise!
          // https://github.com/angular-ui/ui-router/wiki/Quick-Reference#stategoto--toparams--options
          var goPromise = $state.go('dashboard');
    
          $log.log("$state.go: ", goPromise);
    
          return goPromise;
        }
    
        // using $promise returned by resource, and adding an .then
        // (resolves when the $resource does)
        function somethingElse() {
          var resourceContainingPromise = $resource('https://api.stackexchange.com/2.2/info')
            .get({
              site: '*'
            });
    
          // (note that it contains a $promise, is not a promise itself)
          $log.log("$resource: ", resourceContainingPromise);
    
          return resourceContainingPromise
            .$promise
            .then(function resourceHandler(results) {
              $scope.status.*Users = results.items[0].total_users;
            });
        }
    
        // returns a rejected promise
        // (immediate resolve)
        function triggerError() {
          var rejectPromise = $q.reject("An error message");
    
          $log.log("$q.reject: ", rejectPromise);
    
          return rejectPromise;
        }
    
        // this gets skipped due to .triggerError()
        function neverReached() {
    
          $log.error("Shouldn't see this!");
    
          $scope.status.loading += "Never reached!";
        }
    
        // this catches the $q.reject and logs the data it passed...
        function catchesReject(data) {
          $log.log(data); //log the error message
          return $state.go('error');
        }
    
      }
    })();
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular-resource.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.14/angular-ui-router.min.js"></script>
    <div ng-app="myApp">
      <div ng-controller="myController">
        <div style="display:inline-block; float:left; margin-right: 20px; min-width: 250px;">
          <ul>
            <li>Email activated: {{status.emailActivated}}</li>
            <li>Sign-in by Token: {{status.signinByToken}}</li>
            <li>Personal info set: {{status.personalInfo}}</li>
            <li>* Users: {{status.*Users}}</li>
          </ul>
          <hr />
          Loading: {{status.loading}}
          <br />Counter: {{status.counter}}
        </div>
        <div style="display:inline-block; padding: 10px; border: 1px solid grey; max-width: 150px;">
          <strong>Ui Router Test</strong>
          <div ui-view></div>
        </div>
      </div>
    </div>

    【讨论】:

    • 我忘了:非常感谢JcT的详细回复!!
    • @balteo 乐于助人!
    【解决方案2】:

    我想我找到了问题的答案。

    首先,需要考虑到 then() 引用文档这一事实,返回一个新的承诺,该承诺通过回调的返回值解决

    见下文:

    then(successCallback, errorCallback, notifyCallback) – 不管 当承诺已经或将要解决或拒绝时,然后调用一个 成功或错误回调在结果后立即异步 可用。使用单个参数调用回调: 结果或拒绝原因。此外,通知回调可能是 调用零次或多次以提供进度指示,在 承诺已解决或被拒绝。

    此方法返回一个新的承诺,该承诺通过以下方式解决或拒绝 successCallback、errorCallback 的返回值(除非 value 是一个 Promise,在这种情况下,它使用以下值解决 使用 Promise 链接在该 Promise 中解决)。它还通知 通过 notifyCallback 方法的返回值。承诺不能 通过 notifyCallback 方法解决或拒绝。

    所以我假设下面的回调(确实返回一些明确的东西)只会返回 undefined 本身,它被 then() 包装成一个承诺:

    var goToDashboard = function () {
        //TODO: use $q here?
        $state.go('dashboard');
    };
    

    所以我确实有一个承诺 - 感谢then() - 我不需要其他任何东西......

    【讨论】:

    最近更新 更多