Promise 链接和处理不返回承诺链下任何内容的操作

Promise chaining and dealing with an action not returning anything down the chain of promises

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

我需要把那个动作包装在一个承诺中吗?我需要这样的东西吗:

$q.when();

查看下面我的代码:

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

有人可以指点一下吗?

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

首先,需要考虑 then() 这一事实,引用文档,return 是一个用 return 解决的新承诺回调值.

见下文:

then(successCallback, errorCallback, notifyCallback) – regardless of when the promise was or will be resolved or rejected, then calls one of the success or error callbacks asynchronously as soon as the result is available. The callbacks are called with a single argument: the result or rejection reason. Additionally, the notify callback may be called zero or more times to provide a progress indication, before the promise is resolved or rejected.

This method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback (unless that value is a promise, in which case it is resolved with the value which is resolved in that promise using promise chaining). It also notifies via the return value of the notifyCallback method. The promise cannot be resolved or rejected from the notifyCallback method.

所以我假设以下回调(明确地 return 某些事情)将 return undefined 本身包装到 then() 的承诺中:

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

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

在下文中,我演示了使用来自各种函数和其他 return 类型的承诺链接 .then。没有 return 承诺,当然,没有延迟解决,下面的 .then 立即执行 - 所以如果你有一些异步需要完成,你需要 return 承诺异步任务完成时解决。请注意 returning $q.when() 做了 return 承诺(包装在您作为参数提供的任何内容周围),但它会立即得到解决。

另外,请注意$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,
      WhosebugUsers: 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: 'Whosebug'
        });

      // (note that it contains a $promise, is not a promise itself)
      $log.log("$resource: ", resourceContainingPromise);

      return resourceContainingPromise
        .$promise
        .then(function resourceHandler(results) {
          $scope.status.WhosebugUsers = 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>Whosebug Users: {{status.WhosebugUsers}}</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>