Promises与Javascript异步编程

在如今都追求用户体验的时代,Ajax应用真的是无所不在。加上这些年浏览器技术、HTML5以及CSS3等的发展,越来越多的富Web应用出现;在给与我们良好体验的同时,Web开发人员在背后需要处理越来越多的异步回调逻辑。

笔者对最近读完的《Async Javascript-Build More Responsive Apps with Less Code》(Javascript异步编程-设计快速响应的网络应用)一书以及部分资料,整理了我认为比较重要的一些点以及容易理解错的地方,使大家对Promise对象以及异步编程有更深的认识。

 

嵌套式回调

我们都写过这样的函数:

1 setTimeout(function() {
2     setTimeout(function() {
3         // do something
4     }, 10)
5 }, 100);
6  
7 $.ajax(url, function() {
8     $.ajax(url2, function() {
9         $.ajax(url3, function() {
10             // do something
11         });
12     });
13 });

可是问题来了,当我们的嵌套越多,代码结构层级会变得越来越深。首先是阅读上会变得困难;其次是强耦合,接口变得不好扩展。我们需要一种模式来解决这种问题,这就是Promises所要做的事情。

 

 

异步函数类型

Javascript里异步函数可以分为两大类型:

  • I/O函数(Ajax、script…)
  • 计时函数(setTimeout、setInterval、setImmediate)

 

异步函数异常捕获

先看个例子:

1 try {
2     setTimeout(function A() {
3         setTimeout(function B() {
4             setTimeout(function C() {
5                 throw new Error('Error');
6             }, 0);
7         }, 0);
8     }, 0);
9 catch (e) {}

运行以上代码,A、B、C被添加到事件队列里;异常触发时,A、B已被移出事件队列,内存堆栈里只存在C,此时的异常不被try捕获,只会流向应用程序未捕获异常处理器。

所以,在异步函数里,不能使用try/catch捕获异常。

 

分布式事件

Javascript的事件核心是事件分发机制,通过对发布者绑定订阅句柄来达到异步相响应的目的:

1 document.onclick = function() {
2     // click
3 };

PubSub(Publish/Subscribe, 发布/订阅)模式,就是这么一种模式,通过订阅发布者的事件响应来达到多层分发解耦的目的。

以下是一个简单版本的PubSub模式实现:

1 var PubSub = (function() {
2     var _handlers = {};
3  
4     return {
5         // 订阅事件
6         on: function(eventType, handler) {
7             if (!_handlers[eventType]) {
8                 _handlers[eventType] = [];
9             }
10             if (typeof handler == 'function') {
11                 _handlers[eventType].push(handler);
12             }
13         },
14         // 发布事件
15         emit: function(eventType) {
16             var args = Array.prototype.slice.call(arguments, 1);
17             var handlers = _handlers[eventType] || [];
18  
19             for (var i = 0, len = handlers.length; i < len; i++) {
20                 handlers[i].apply(null, args)
21             }
22         }
23     };
24 })();

 

Promises/A规范

CommonJS之Promises/A规范是Kris Zyp于2009年提出来的,它通过规范API接口来简化异步编程,使我们的异步逻辑代码更易理解。

遵循Promises/A规范的实现我们称之为Promise对象,Promise对象有且仅有三种状态:unfulfilled(未完成)、fulfilled(已完成)、failed(失败/拒绝);而且状态变化只能从unfulfilled到fulfilled,或者unfulfilled到failed;

Promise对象需实现一个then接口,then(fulfilledHandler, errorHandler, progressHandler);then接口接收一个成功回调(fulfilledHandler)与一个失败回调(errorHandler);progressHandler触发回调是可选的,Promise对象没有强制去回调此句柄。

then方法的实现需要返回一个新的Promise对象,以形成链式调用,或者叫Promise管道。

为了实现状态的转变,我们还需要实现另外两个接口:

  • resolve:实现状态由未完成到已完成
  • reject:实现状态由未完成到拒绝(失败)

这样子我们开篇所说的嵌套式回调就可以这样子写了:

1 // 这里假设Promise是一个已实现的Promise对象
2 function asyncFn1() {
3     var p = new Promise();
4     setTimeout(function() {
5         console.log(1);
6         p.resolve(); // 标记为已完成
7     }, 2000);
8     return p;
9 }
10 function asyncFn2() {
11     var p = new Promise();
12     setTimeout(function() {
13         console.log(2);
14         p.reject('error'); // 标记为拒绝
15     }, 1000);
16     return p;
17 }
18  
19 asyncFn1()
20     .then(function() {
21         return asyncFn2();
22     }).then(function() {
23         console.log('done');
24     }, function(err) {
25         console.log(err);
26     });

有了Promise,我们可以以同步的思维去编写异步的逻辑了。在同步函数的世界里,有2个非常重要的概念:

  • 有返回值
  • 可以抛出异常

Promise不仅仅是一种可以链式调用的对象,更深层次里,它为异步函数与同步函数提供了一种更加直接的对应关系。

上面我们说过,在异步函数里,不能使用try/catch捕获异常,因此也不能抛出异常。有了Promise,只要我们显式定义了errorHandler,那么我们就可以做到像同步函数那样的异常捕获了。

像上面的例子,相当于以下同步代码:

1 try {
2     asyncFn1();
3     asyncFn2();
4     console.log('done');
5 catch (e) {
6     console.log(e);
7 }

 

还有几点需要注意,在Promises/A规范里有这么一段:

This function should return a new promise that is fulfilled when the given fulfilledHandler or errorHandler callback is finished. This allows promise operations to be chained together. The value returned from the callback handler is the fulfillment value for the returned promise. If the callback throws an error, the returned promise will be moved to failed state.

注意里面的new,then接口需要返回的是一个新的Promise对象,而不是已有的Promise对象。在jQuery1.8以前的版本中,返回的是一个状态改变过的已有Promise对象。这有什么问题呢?
问题在于多个调用者之间可以通过改变状态来影响另外的调用者。

Once a promise is fulfilled or failed, the promise’s value MUST not be changed, just as a values in JavaScript, primitives and object identities, can not change (although objects themselves may always be mutable even if their identity isn’t).

另外,promise一旦完成或失败,promise的值就不可更改。就像Javascript中的数值、 基元以及对象ID等都是不可更改的,这有利于维护调用者之间的关系不受影响。

 

以下是几个遵循Promises/A规范的类库,大家可以尝试去了解下

  • when:A solid, fast Promises/A+ and when() implementation, plus other async goodies.
  • q:A tool for making and composing asynchronous promises in JavaScript
  • rsvp.js:A lightweight library that provides tools for organizing asynchronous code

原文 :http://www.zawaliang.com/2013/08/399.html



你可能感兴趣的:(JavaScript,AngularJS,Promise,异步)