使用 Promises 编写更好的 JavaScript 代码 已翻译 100%

oschina 投递于 2014/04/19 08:07 (共 5 段, 翻译完成于 04-19)
阅读 5498
收藏 122
4
加载中

你可能已经无意中听说过 Promises 是多么的代表未来。所有酷孩子们都使用它们,但你不知道为什么它们如此特别。难道你不能使用回调么?有什么了不起的?在本文中,我们将看看promises是什么以及如何使用它们写出更好的JavaScript。

Promises易于阅读

比如说我们想从HipsterJesus的API中抓取一些数据并将这些数据添加到我们的页面中。这些API的响应数据形式如下:

{
  "text": "<p>Lorem ipsum...</p>",
  "params": {
    "paras": 4,
    "type": "hipster-latin"
  }}

要使用回调的话,我们通常要写如下形式的东西:

$.getJSON('http://hipsterjesus.com/api/', function(data) {
  $('body').append(data.text);
});

如果你有jQuery的使用经历,你会认出我们创建了一个GET请求并且希望响应内容是JSON。我们还传递了一个回调函数来接受响应的JSON,以将数据添加到文档中。

Garfielt
翻译于 2014/04/19 09:15
1

另外一种书写方法是使用getJSON方法返回的promise对象。你可以直接在这个返回对象上绑定一个回调。

var promise = $.getJSON('http://hipsterjesus.com/api/');promise.done(function(data) {
  $('body').append(data.text);
});

在上面的回调例子中,当响应成功时它将API请求的结果添加到文档中。但当响应失败是会发生什么呢?我们可以在我们的promise上绑定一个失败处理器。

var promise = $.getJSON('http://hipsterjesus.com/api/');promise.done(function(data) {
  $('body').append(data.text);});promise.fail(function() {
  $('body').append('<p>Oh no, something went wrong!</p>');
});

大多数人删掉了promise变量,这样更简洁,一眼就能看出代码的作用。

$.getJSON('http://hipsterjesus.com/api/').done(function(data) {
  $('body').append(data.text);}).fail(function() {
  $('body').append('<p>Oh no, something went wrong!</p>');
});

jQuery也包含一个一直发生的事件处理器,不论请求成功失败都会被调用。

$.getJSON('http://hipsterjesus.com/api/').done(function(data) {
  $('body').append(data.text);}).fail(function() {
  $('body').append('<p>Oh no, something went wrong!</p>');}).always(function() {
  $('body').append('<p>I promise this will always be added!.</p>');
});

通过使用promise,回调的顺序是按预期的。我们能确保正常回调先被调用,然后是失败回调,最后是一直发生的回调。

Garfielt
翻译于 2014/04/19 09:26
1

更好的API

比如说我们想创造一个HipsterJesus API的封装对象。我们会添加一个方法——html,它将来自API的HTML数据返回。与之前设置一个回调处理器来解析请求不同,我们可以让方法返回一个promise对象。

var hipsterJesus = {
  html: function() {
    return $.getJSON('http://hipsterjesus.com/api/').then(function(data) {
      return data.text;
    });
  }};

这个做法很酷,这样我们可以绕过promise对象而不必担心何时或如何解析它的值。任何需要promise返回值的代码只需注册一个成功响应回调即可。

then方法允许我们修改promise的结果并将其传递给链中的下一个处理器。这意味现在我们可以这样使用新的API:

hipsterJesus.html().done(function(html) {
  $("body").append(html);
});

直到最近,AngularJS出现了一个杀手级特性,模板可以直接绑定到promise。在Angular的控制器中,像这样:

$scope.hipsterIpsum = $http.get('http://hipsterjesus.com/api/');

这样,在模板中写{{ hipsterIpsum.text }}就很简单了。当promise解析后,Angular不需要自动更新视图。不幸的是Angular团队已经放弃了这一特性。现在,它可以通过调用$parseProvider.unwrapPromises(true)来启用。我希望Angular已经其他框架一直包含此特性(我会一直留意)。

Garfielt
翻译于 2014/04/19 09:42
1

promise最出彩的部分是你可以将它们串联起来。比如说我们想添加一个方法到一个返回一段数组的API。

var hipsterJesus = {

  html: function() {
    return $.getJSON('http://hipsterjesus.com/api/').then(function(data) {
      return data.text;
    });
  },

  paragraphs: function() {
    return this.html().then(function(html) {
      return html.replace(/<[^>]+>/g, "").split("");
    });
  }};

我们以上面的方式这种HTML方法,我们用它在paragraphs方法中。因为promise回调函数的返回值将传递给链中的下一个回调,我们能够在通过它们时自由地创建小的、功能性的方法来改变数据。

我们可以按需求任意次串联promise。让我们添加一个。

var hipsterJesus = {

  html: function() {
    return $.getJSON('http://hipsterjesus.com/api/').then(function(data) {
      return data.text;
    });
  },

  paragraphs: function() {
    return this.html().then(function(html) {
      return html.replace(/<[^>]+>/g, "").split("");
    });
  },

  sentences: function() {
    return this.paragraphs().then(function(paragraphs) {
      return [].concat.apply([], paragraphs.map(function(paragraph) {
        return paragraph.split(/. /);
      }));
    });
  }};
Garfielt
翻译于 2014/04/19 09:50
1

多个调用

可能promise最显著的特点是调用多个API的能力。当使用回调时,如果你需要同时创建两个API调用时会发生什么呢?你可能会这样写:

var firstData = null;var secondData = null;var responseCallback = function() {

  if (!firstData || !secondData)
    return;

  // do something}$.get("http://example.com/first", function(data) {
  firstData = data;
  responseCallback();});$.get("http://example.com/second", function(data) {
  secondData = data;
  responseCallback();
});

使用promise的话,这就简单多了:

var firstPromise = $.get("http://example.com/first");
var secondPromise = $.get("http://example.com/second");
$.when(firstPromise, secondPromise).done(function(firstData, secondData) {
  // do something
});

这里我们使用when方法,将其绑定到一个供两个请求都完成时调用的处理器上。

结论

这就是promise。希望你马上就想到一些可以用promise实现的的可怕的事情。你最喜欢使用它们的方式是什么?在评论中告诉我吧!

*注:为简单起见,本文使用了jQuery的延期执行。jQuery的Deferred对象Promises/A+的规范间有细微的差别,这个规范更标准。更多信息,查看jQuery维基上的问答。

Garfielt
翻译于 2014/04/19 09:58
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(16)

p
penJunTan
在 angular 1.2 已经不支持 Templates no longer automatically unwrap promises
justplaymore
justplaymore
不懂请看 http://javascript.ruanyifeng.com/advanced/asynchronous.html#toc4
GuoYin
GuoYin

文章中所提到的promise其实就是jquery官方文档中所提到的Deferred,这是jquery很早版本就有的一个特性,支持链式操作。

隐性精神病人
隐性精神病人

引用来自“xcchcaptain”的评论

翻译功力不错,但是文章选材,再好点
很难直接感受到适用promise与传统代码方式的优势啊

引用来自“xiongxin8802”的评论

能说说这个到底是个什么东西吗,我看的云里雾里的

推荐你看看jquery官网的一篇教程 http://learn.jquery.com/code-organization/deferreds/jquery-deferreds/ 我也是才接触这东西,好多还理解不了 是CommonJs规范的一部分呢 ,jquery的内部的ajax部分都遵循了这个规范

进击的代码
进击的代码

引用来自“xcchcaptain”的评论

翻译功力不错,但是文章选材,再好点
很难直接感受到适用promise与传统代码方式的优势啊

引用来自“xiongxin8802”的评论

能说说这个到底是个什么东西吗,我看的云里雾里的

引用来自“进击的代码”的评论

话说我看了半天也没具体明白,网上一查介绍是说做异步请求编程的http://blogs.msdn.com/b/ie/archive/2011/09/11/asynchronous-programming-in-javascript-with-promises.aspx

引用来自“xiongxin8802”的评论

非常感谢你哦

引用来自“yifei”的评论

http://www.oschina.net/translate/whats-so-great-about-javascript-promises

十分感谢~~

程序员乙
程序员乙

引用来自“xcchcaptain”的评论

翻译功力不错,但是文章选材,再好点
很难直接感受到适用promise与传统代码方式的优势啊

引用来自“xiongxin8802”的评论

能说说这个到底是个什么东西吗,我看的云里雾里的

引用来自“进击的代码”的评论

话说我看了半天也没具体明白,网上一查介绍是说做异步请求编程的http://blogs.msdn.com/b/ie/archive/2011/09/11/asynchronous-programming-in-javascript-with-promises.aspx

引用来自“xiongxin8802”的评论

非常感谢你哦

http://www.oschina.net/translate/whats-so-great-about-javascript-promises

朱__朱
朱__朱

翻译有点生硬,但基本能看懂。

换个ID
换个ID

正好最近利用jQuery的延期执行在多个模板与数据后都获取完成后再统一处理页面渲染的效果

唯一
唯一

完全没看懂呀,期待更加权威的解释

walkskyer
walkskyer

确实没看明白是啥玩意儿。

返回顶部
顶部