/ javascript

JavaScript Promises 初体验

Promise 是什么?

Promise 对象用来进行延迟(deferred) 和 异步(asynchronous) 计算。

一个 Promise 处于以下三种状态之一:

  • pending: 初始状态, 非 fulfilled 或 rejected.
  • fulfilled: 成功的操作.
  • rejected: 失败的操作.

Promise 接口表示为一个值的代理,这个值在promise创建时未必已知. 它允许你将 handlers 与一个异步 action 最终的成功或失败状态关联起来. 这使得异步方法可以像同步方法那样返回值: 异步方法返回一个在未来某个时刻拥有一个值的 promise 来替代最终返回值.

pending状态的promise既可带着一个值成为 fulfilled 状态,也可带着一个reason成为 rejected 状态. 任意情况发生时, 通过promise的 then 方法所排列(queued up)的相应handlers 就会被调用. (当绑定相应的 handler 时,如果 promise 已经处于 fulfilled 或 rejected 状态这个 handler 将会被调用, 所以在异步操作的完成和它的 handler 的绑定之间不存在竞争条件.)

因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回 promises, 所以它们可以被链式调用—一种被称为 composition 的操作.

promises.png

Promise 为何出现?

简单的说,Promise 被当成 callback hell 的救命仙丹。

回调函数是JavaScript的一大特色! node.js官方的api基本都是以会回调方式传递函数返回值。习惯同步编程的对这种异步方式多少会产生水土不服,而且层层嵌套,不知不觉就建造起了一座高高的回调金字塔。针对这种普遍问题,Promise应势而生!

Promise 基本用法

创建 Promise

{% highlight javascript %}

var promise = new Promise(function(resolve, reject) {
  // 做一些异步操作的事情,然后……

  if (/* 一切正常 */) {
    resolve("Stuff worked!");
  }
  else {
    reject(Error("It broke"));
  }
});

{% endhighlight %}

Promise 的构造器接受一个函数作为参数,它会传递给这个回调函数两个变量 resolve 和 reject。在回调函数中做一些异步操作,成功之后调用 resolve,否则调用 reject。

调用 reject 的时候传递给它一个 Error 对象只是个惯例并非必须,这和经典 JavaScript 中的 throw 一样。传递 Error 对象的好处是它包含了调用堆栈,在调试的时候会有点用处。

使用 Promise:

{% highlight javascript %}

promise.then(function(result) {
  console.log(result); // “完美!”
}, function(err) {
  console.log(err); // Error: "出问题了"
});

{% endhighlight %}

“then”接受两个参数,成功的时候调用一个,失败的时候调用另一个,两个都是可选的,所以你可以只处理成功的情况或者失败的情况。

Promise 实践

  1. 值的处理

你可以对结果做些修改然后返回一个新值:

{% highlight javascript %}

var promise = new Promise(function(resolve, reject) {
  resolve(1);
});

promise.then(function(val) {
  console.log(val); // 1
  return val + 2;
}).then(function(val) {
  console.log(val); // 3
});

{% endhighlight %}

  1. 队列的异步操作

你也可以把“then”串联起来依次执行异步操作。

当你从“then”的回调函数返回的时候,这里有点小魔法。如果你返回一个值,它就会被传给下一个“then”的回调;而如果你返回一个“类 Promise”的对象,则下一个“then”就会等待这个 Promise 明确结束(成功/失败)才会执行。例如:

你也可以把“then”串联起来依次执行异步操作。

当你从“then”的回调函数返回的时候,这里有点小魔法。如果你返回一个值,它就会被传给下一个“then”的回调;而如果你返回一个“类 Promise”的对象,则下一个“then”就会等待这个 Promise 明确结束(成功/失败)才会执行。例如:

返回一个值

{% highlight javascript %}

getJSON('story.json').then(function(story) {
  return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
  console.log("Got chapter 1!", chapter1);
});

{% endhighlight %}

你返回一个“类 Promise”的对象

{% highlight javascript %}

var storyPromise;

function getChapter(i) {
  storyPromise = storyPromise || getJSON('story.json');

  return storyPromise.then(function(story) {
    return getJSON(story.chapterUrls[i]);
  })
}

// 用起来非常简单:
getChapter(0).then(function(chapter) {
  console.log(chapter);
  return getChapter(1);
}).then(function(chapter) {
  console.log(chapter);
});

{% endhighlight %}

  1. 错误处理

前面已经看到,“then”接受两个参数,一个处理成功,一个处理失败(或者说肯定和否定,按 Promise 术语):

{% highlight javascript %}

get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.log("Failed!", error);
});

{% endhighlight %}

你还可以使用“catch”:

{% highlight javascript %}

get('story.json').then(function(response) {
  console.log("Success!", response);
}).catch(function(error) {
  console.log("Failed!", error);
});

{% endhighlight %}

Promise 被否定之后会跳转到之后第一个配置了否定回调的 then(或 catch,一样的)。对于 then(func1, func2) 来说,必会调用 func1 或 func2 之一,但绝不会两个都调用。而 then(func1).catch(func2) 这样,如果 func1 返回否定的话 func2 也会被调用,因为他们是链式调用中独立的两个步骤。看下面这段代码:

{% highlight javascript %}

asyncThing1().then(function() {
  return asyncThing2();
}).then(function() {
  return asyncThing3();
}).catch(function(err) {
  return asyncRecovery1();
}).then(function() {
  return asyncThing4();
}, function(err) {
  return asyncRecovery2();
}).catch(function(err) {
  console.log("Don't worry about it");
}).then(function() {
  console.log("All done!");
});

{% endhighlight %}

Promise API 参考

静态方法

Promise.resolve(promise);
返回一个 Promise(当且仅当 promise.constructor == Promise)

Promise.resolve(thenable);
从 thenable 对象创建一个新的 Promise。一个 thenable(类 Promise)对象是一个带有“then”方法的对象。

Promise.resolve(obj);
创建一个以 obj 为肯定结果的 Promise。

Promise.reject(obj);
创建一个以 obj 为否定结果的 Promise。为了一致性和调试便利(如堆栈追踪),obj 应该是一个 Error 实例对象。

Promise.all(array);
创建一个 Promise,当且仅当传入数组中的所有 Promise 都肯定之后才肯定,如果遇到数组中的任何一个 Promise 以否定结束,则抛出否定结果。每个数组元素都会首先经过 Promise.resolve,所以数组可以包含类 Promise 对象或者其他对象。肯定结果是一个数组,包含传入数组中每个 Promise 的肯定结果(且保持顺序);否定结果是传入数组中第一个遇到的否定结果。

Promise.race(array);
创建一个 Promise,当数组中的任意对象肯定时将其结果作为肯定结束,或者当数组中任意对象否定时将其结果作为否定结束。

Promise 的现行解决方案

其实已经有一些第三方库实现了 Promise 的功能:

上面这些库和 JavaScript 原生 Promise 都遵守一个通用的、标准化的规范:Promises/A+,jQuery 有个类似的方法叫 Deferred,但不兼容 Promises/A+ 规范,于是会有点小问题,使用需谨慎。jQuery 还有一个 Promise 类型,它其实是 Deferred 的缩减版,所以也有同样问题。

目前的浏览器已经(部分)实现了 Promise。

Chrome 32、Opera 19 和 Firefox 29 以上的版本已经默认支持 Promise。由于是在 WebKit 内核中所以我们有理由期待下个版本的 Safari 也会支持,并且 IE 也在不断的开发中。

要在这两个浏览器上达到兼容标准 Promise,或者在其他浏览器以及 Node.js 中使用 Promise,可以看看这个 polyfill(gzip 之后 2K)。

相关参考