如何利用Promises编写更优雅的JavaScript代码
你可能已经无意中听说过Promises,很多人都在讨论它,使用它,但你不知道为什么它们如此特别。难道你不能使用回调么?有什么了特别的?在本文中,我们一起来看看Promises是什么以及如何使用它们写出更优雅的JavaScript代码。
Promises易于阅读
比如说我们想从HipsterJesus的API中抓取一些数据并将这些数据添加到我们的页面中。这些API的响应数据形式如下:
{ "text":"<p>Loremipsum...</p>", "params":{ "paras":4, "type":"hipster-latin" }}
要使用回调的话,我们通常要写如下形式的东西:
$.getJSON('http://hipsterjesus.com/api/',function(data){ $('body').append(data.text); });
如果你有jQuery的使用经历,你会认出我们创建了一个GET请求并且希望响应内容是JSON。我们还传递了一个回调函数来接受响应的JSON,以将数据添加到文档中。
另外一种书写方法是使用getJSON方法返回的promise对象。你可以直接在这个返回对象上绑定一个回调。
varpromise=$.getJSON('http://hipsterjesus.com/api/');promise.done(function(data){ $('body').append(data.text); });
在上面的回调例子中,当响应成功时它将API请求的结果添加到文档中。但当响应失败是会发生什么呢?我们可以在我们的promise上绑定一个失败处理器。
varpromise=$.getJSON('http://hipsterjesus.com/api/');promise.done(function(data){ $('body').append(data.text);});promise.fail(function(){ $('body').append('<p>Ohno,somethingwentwrong!</p>'); });
大多数人删掉了promise变量,这样更简洁,一眼就能看出代码的作用。
$.getJSON('http://hipsterjesus.com/api/').done(function(data){ $('body').append(data.text);}).fail(function(){ $('body').append('<p>Ohno,somethingwentwrong!</p>'); });
jQuery也包含一个一直发生的事件处理器,不论请求成功失败都会被调用。
$.getJSON('http://hipsterjesus.com/api/').done(function(data){ $('body').append(data.text);}).fail(function(){ $('body').append('<p>Ohno,somethingwentwrong!</p>');}).always(function(){ $('body').append('<p>Ipromisethiswillalwaysbeadded!.</p>'); });
通过使用promise,回调的顺序是按预期的。我们能确保正常回调先被调用,然后是失败回调,最后是一直发生的回调。
更好的API
比如说我们想创造一个HipsterJesusAPI的封装对象。我们会添加一个方法——html,它将来自API的HTML数据返回。与之前设置一个回调处理器来解析请求不同,我们可以让方法返回一个promise对象。
varhipsterJesus={ html:function(){ return$.getJSON('http://hipsterjesus.com/api/').then(function(data){ returndata.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已经其他框架一直包含此特性(我会一直留意)。
链式调用
Promise最出彩的部分是你可以将它们串联起来。比如说我们想添加一个方法到一个返回一段数组的API。
varhipsterJesus={ html:function(){ return$.getJSON('http://hipsterjesus.com/api/').then(function(data){ returndata.text; }); }, paragraphs:function(){ returnthis.html().then(function(html){ returnhtml.replace(/<[^>]+>/g,"").split(""); }); }};
我们以上面的方式这种HTML方法,我们用它在paragraphs方法中。因为promise回调函数的返回值将传递给链中的下一个回调,我们能够在通过它们时自由地创建小的、功能性的方法来改变数据。
我们可以按需求任意次串联promise。让我们添加一个。
varhipsterJesus={ html:function(){ return$.getJSON('http://hipsterjesus.com/api/').then(function(data){ returndata.text; }); }, paragraphs:function(){ returnthis.html().then(function(html){ returnhtml.replace(/<[^>]+>/g,"").split(""); }); }, sentences:function(){ returnthis.paragraphs().then(function(paragraphs){ return[].concat.apply([],paragraphs.map(function(paragraph){ returnparagraph.split(/./); })); }); }};
多个调用
可能promise最显著的特点是调用多个API的能力。当使用回调时,如果你需要同时创建两个API调用时会发生什么呢?你可能会这样写:
varfirstData=null;varsecondData=null;varresponseCallback=function(){ if(!firstData||!secondData) return; //dosomething}$.get("http://example.com/first",function(data){ firstData=data; responseCallback();});$.get("http://example.com/second",function(data){ secondData=data; responseCallback(); });
使用promise的话,这就简单多了:
varfirstPromise=$.get("http://example.com/first"); varsecondPromise=$.get("http://example.com/second"); $.when(firstPromise,secondPromise).done(function(firstData,secondData){ //dosomething });
这里我们使用when方法,将其绑定到一个供两个请求都完成时调用的处理器上。
结论
这就是Promise。希望你马上就想到一些可以用Promise实现的的可怕的事情。你最喜欢使用它们的方式是什么?在评论中告诉我吧!
*注:为简单起见,本文使用了jQuery的延期执行。jQuery的Deferred对象和Promises/A+的规范间有细微的差别,这个规范更标准。
以上这篇如何利用Promises编写更优雅的JavaScript代码就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。