AngularJS之道 - Promise编程模式

转自:
lxjwlt's blog(2015-06-23):http://blog.lxjwlt.com/front-...html

AngularJS经过内置的$q服务提供Promise编程模式。经过将异步函数注册到promise对象,Promise编程模式提供一种链式调用异步函数的方式。angularjs

初始化:ajax

<html>
    <head>
        <title>Promise fun</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.5/angular.min.js"></script>
        <script src="app.js"></script>
    </head>
    <body ng-app="app">
    </body>
</html>

function getData($timeout, $q) {
  return function() {
    var defer = $q.defer()

    // 模拟异步函数
    $timeout(function() {
    }, 2000)

    return defer.promise
  }
}

angular.module('app', [])
    .factory('getData', getData)
    .run(function(getData) {
        var promise = getData();
    })

为了简单起见,咱们使用$timeout()服务来模拟异步函数。但实际而言,Promise模式最多见的应用场景是运用$http服务的AJAX回调函数。编程

defer对象api

defer对象只是一种暴露promise对象和promise对象相关方法的对象。defer对象经过调用$q.deferred()方法构造出来,并且defer对象暴露了三个主要方法:resolve(),reject(),和notify()。对应的promise对象能够经过.promise属性访问。promise

咱们可使用defer对象发送“异步函数成功完成”的信号:app

function getData($timeout, $q) {
  return function() {
    var defer = $q.defer()

    // 模拟异步函数
    $timeout(function() {
      defer.resolve('data received!')
    }, 2000)

    return defer.promise
  }
}

这里,咱们先建立了一个新的defer对象,而后返回它的.promise属性。同时,咱们执行咱们的异步函数,当在该函数完成的时候,咱们执行defer对象的resolve()方法。resolve()函数的参数会被传递给接下来的回调函数。框架

promise对象dom

如今,咱们获得了一个promise对象(经过defer.promise获得),接下来,让咱们注册一个回调函数,该回调函数会在异步函数执行完成后被调用。异步

使用then()方法为promise对象绑定一个回调函数,该回调函数将返回的字符串打印出来:

.run(function(getData) {
  var promise = getData()
    .then(function(string) {
      console.log(string)
    })
})

如今,当你刷新页面,两秒后你会看到控制台打印出"data recived!"。

reject一个promise对象

注:简而言之,promise的resolve是发出“执行成功”的信号,而reject则是发出“执行失败”的信号。当信号发出,promise会根据不一样的信号,执行不一样的回调函数。

咱们已经知道如何resolve一个promise对象,但若是一个异步函数调用失败了,会怎么样呢?

咱们使用Math.random()函数模拟promise对象有50%的机会被reject:

function getData($timeout, $q) {
  return function() {
    var defer = $q.defer()

    // 模拟异步函数
    $timeout(function() {
      if(Math.round(Math.random())) {
        defer.resolve('data received!')
      } else {
        defer.reject('oh no an error! try again')
      }
    }, 2000)
    return defer.promise
  }
}

then()方法的第二个参数能够(可选的)接受一个错误处理回调函数,当promise被reject时才会调用该回调函数。

将错误处理函数做为第二参数传给then():

.run(function(getData) {
  var promise = getData()
    .then(function(string) {
      console.log(string)
    }, function(error) {
      console.error(error)
    })
})

如今,若是你再次刷新页面,你有50%的概率能看到错误消息!

经过调用多个不一样then()方法,在同一个promise对象上能够注册多个回调函数。这些函数会按照他们注册的顺序一一被调用。

使用$q构造函数

$q服务自己也是一个函数,它可以让你快速的将一个异步回调函数转换成一个基于Promise模式的函数。

将这个模拟异步函数重写成一个使用$q()返回promise对象的函数:

function getData($timeout, $q) {
  return function() {

    // 模拟异步函数
    return $q(function(resolve, reject) {
      $timeout(function() {
        if(Math.round(Math.random())) {
          resolve('data received!')
        } else {
          reject('oh no an error! try again')
        }
      }, 2000)
    })

  }
}

这个方法实现的效果跟手动建立defer对象的效果是同样的 -- 你采用哪一种方式取决于你的偏好和你是否想要在代码中使用notify()。

finally方法

Promise模式保证成功回调函数和错误回调函数中,其中一定有一个会被执行,但二者永远不会同时执行。若是你须要确保不论promise对象的结果如何,都执行某一个特殊的函数,那该怎么办?你能够在promise对象上注册一个finally()方法。对于将代码重置为可知状态下,这方法是很是有帮助的。

使用finally()方法将异步函数完成时的时间戳打印出来:

.run(function(getData) {
  var promise = getData()
    .then(function(string) {
      console.log(string)
    }, function(error) {
      console.error(error)
    })
    .finally(function() {
      console.log('Finished at:', new Date())
    })
})

无论promise对象是被resolve仍是被reject,你都能看到控制台打印出当前时间。

Promise链式编程

Promise模式一个很是强大的特性是可以链式编写回调函数。这个特性使数据可以在回调链的每一步上进行传递,处理和改变。虽然这一语法很是容易理解,但有时候这语法也会使人困惑。

让咱们先看一个基础例子。

首先,咱们对咱们的异步函数进行修改,咱们给resolve函数传入一个0-9之间的随机数(再也不是"data received"字符串):

function getData($timeout, $q) {
  return function() {
    // 模拟异步函数
    return $q(function(resolve, reject) {
      $timeout(function() {
        resolve(Math.floor(Math.random() * 10))
      }, 2000)
    })
  }
}

当页面刷新,你应该可以看到一个0-9之间的整数被打印出来。

为了链式调用,咱们须要修改回调函数,使其可以返回一个值。

修改promise回调函数,使其返回上述随机数乘以2的值:

.run(function(getData) {
  var promise = getData()
    .then(function(num) {
      console.log(num)
      return num * 2
    })
})

如今,咱们可使用then()函数将另外一个回调函数绑定到咱们的promise对象上,该函数会在第一个回调函数返回值时被调用。上述随机数两倍的值会传递到第二个回调函数中:

.run(function(getData) {
  var promise = getData()
    .then(function(num) {
      console.log(num)
      return num * 2
    })
    .then(function(num) {
      console.log(num) // = random number * 2
    })
})

虽然这只是一个简单的例子,可是它阐述了一个很是强大的概念。此外,你不但能够从promise回调函数中返回一个简单的值,你还可以返回一个新的promise对象。那么,promise链会“暂停”直到这个返回的promise对象被resolve。这个特性使你可以链式编写多个异步函数调用(好比多个服务端请求)。

总结

在angularJS框架中,Promise模式编程已经发挥起重要的做用,随着ES6的发布,未来Promise模式在JavaScript中的做用也会愈来愈重要。虽然它一开始看起来难以理解(尤为是链式调用),但promise模式为解决异步代码提供了一套直观且简洁的接口,也正因如此,Promise成为了现代JavaScript中的一个基础构建模块。

相关文章
相关标签/搜索