对中国开发者最具吸引力的科技企业有哪些?快来为你 pick 的企业投票! 了解详情
写点什么

承诺特性:在浏览器中使用 JavaScript 进行异步工作的新标准?

2014 年 5 月 05 日

每个使用 JavaScript 进行进阶编程的人,可能都会遇到过 _ 异步编程 _:与立刻返回结果值的函数不同,在异步编程中我们传递了一个回调函数,它会在稍后某个时刻(当结果可用的时候)被调用。在 JavaScript 世界中,围绕着如何恰当地使用异步 API 构建大规模应用,一直存在着许多争论。然而,最近在 ECMAScript6 中增加了承诺特性的原生支持(目前最新版本的 Chrome、Firefox 和 Opera 已经提供了相应的支持),而且未来的浏览器 API 也将采纳它们。这是否会平息争论?承诺特性现在成为了浏览器环境中的 JavaScript 的“原生”部分,是否也会促使其成为异步应用代码编写的新标准?

问题

什么是异步编程?举例来说,假定我们想要从某个 Web 页面中访问服务器获取 mydata.json 文件。如果读者对浏览器环境中的 Web 开发并不熟悉,那么或许心里会浮现起类似下面代码片段所示的一些 API:

复制代码
var result = http.get("/mydata.json");
console.log("Got result", result);

然而在 Web 中,这种写法要么不可实现,要么会被认为是糟糕的实践。其原因在于,基本上浏览器提供的是单线程运行环境:我们只有一个单一线程,它既负责渲染 Web 页面,又要负责处理事件并执行逻辑。因此,如果我们让这个线程完成开销很大或很缓慢的任务,那么在此期间浏览器就会陷入假死状态。也就是说,如果获取 JSON 文件需要两秒钟,那么这段时间里浏览器除了等待 HTTP 调用外,将无法做任何其他事情。这并不是很好的体验。为了避免这样的问题,JavaScript 中大部分开销比较大的调用,都会以 _ 异步 _API 的形式提供。

一般来说,JavaScript 中异步方面的核心思想在于,不要阻塞主线程并让它等待响应,而是传递一个函数,在稍后(当数据文件被成功获取时)再调用它。因此,当 HTTP 调用正在进行的时候,浏览器可以继续去做其他事情。我们看一下经典的 XMLHTTPRequest 例子:

复制代码
var req = new XMLHttpRequest();
req.open("GET", "/mydata.json", true);
req.onload = function(e) {
var result = req.responseText;
console.log("Got result", result);
};
req.send();

这里的模式是创建一份请求对象,把一个事件监听器附加在上面,监听 load 事件。当它被触发时,我们可以继续之前的任务。在浏览器 API 中,带有事件发射器的请求对象模式颇为常见,但是也还有其他一些模式。例如,让我们看一下用来跟踪用户当前位置的 Geolocation API:

复制代码
navigator.geolocation.getCurrentPosition(function(result) {
console.log("Location", result);
}, function(err) {
console.error("Got error", err);
});

getCurrentPosition API 接受多个回调函数,而不是返回一份请求对象。其中,当成功获得用户位置时,将调用第一个回调函数;而如果出现一些错误,则会调用第二个函数。在服务器端,在异步 API 中传入一个回调函数——这个回调函数接受两个参数(错误和结果)——作为最后一个参数的方式,事实上是 node.js 中的标准做法。例如下面这个是用 node.js 读取文件的例子:

复制代码
var fs = require("fs");
fs.readFile("mydata.json", function(err, content) {
if (err) {
console.error("Got an error", err);
} else {
console.log("Got result", content);
}
});

这些模式都需要面对一些挑战。例如:

  • 需要手动进行错误传播。在 _ 异步 _ 编程风格中,我们可以使用 throw、try 和 catch 来处理和传播错误。这些语言层面的机制无法良好地应对异步流。更糟糕的是,忘记正确地处理错误,将很容易产生错误——从而令进程消失或崩溃。
  • 回调函数永远不会得到执行,或是被多次调用。当我们编写异步函数的时候,往往会意外地忘记调用回调函数,或是调用多次。两种情况都会让调试问题变得非常困难。
  • 回调函数天生会带来深度嵌套的回调。这是可以避免的,但是在异步编码中这也确实是常见问题。

承诺特性(Promises)

承诺特性的目标是简化异步代码的编写。使用承诺特性的 API 不接受回调参数,而是返回一个承诺对象。承诺对象只有少量的方法,其中最重要的是 then(这也正是为何有时候承诺被称为“thenables”)。then 方法接受一个或两个方法,第一个是当承诺被 _ 解决 _(成功)的时候被调用,而第二个则是承诺被 _ 拒绝 _(返回错误)的时候被调用。这两个回调函数都可以完成以下事情:

  • 返回一个新的承诺。在这种情况下,承诺的结果(解决或被拒绝)都被委托给这个新返回的承诺。实际上这可以被用来轻松地链接异步调用,而无须深入嵌套。
  • 返回(非承诺)值。在这种情况下,承诺被解析为该值。
  • 抛出错误。如果在函数体内抛出错误,这将会导致承诺被拒绝。

让我们看看下面这个例子。我们想要跟踪用户的位置,并使用该位置执行 AJAX 调用访问服务器。下面的代码采用了异步编程的常用风格,执行这一任务:

复制代码
navigator.geolocation.getCurrentPosition(function(location) {
var req = new XMLHttpRequest();
req.open("PUT", "/location", true);
req.onload = function() {
console.log("Posted location!");
};
req.onerror = function(e) {
console.error("Putting failed", e);
};
req.send(JSON.stringify(location.coords));
}, function(err) {
console.error("Got error", err);
});

如上所示,我们在这里拥有两个错误处理器。接下里再让我们看一个新的版本,它使用这些 API 基于承诺的假象的版本:

复制代码
navigator.geolocation.getCurrentPosition().then(function(location) {
var req = new XMLHttpRequest();
req.open("PUT", "/location", true);
return req.send(JSON.stringify(location.coords));
}).then(function() {
console.log("Posted location!");
}).then(null, function(err) {
console.error("Got error", err);
});

对于这些基于承诺的 API,有一些需要注意的事情:

  • 嵌套只有一层。
  • 错误处理集中在一处。如果某个错误发生在第一次调用时,它将被传播直到 then 调用处理它。

另外,承诺还有许多其他优点,可以查看文章最后给出的其他阅读材料的链接。

未来

在 Chrome 32、Firefox 29 和 Opera 19 中,浏览器都使用了 Promise 构建器来集成这一特性。此外,未来的浏览器 API 将使用它们。一些例子包括:

随着承诺特性在浏览器 API 中变得越来越流行,它是否会被更多基于浏览器的应用和库采用?jQuery 现在已经有了一套类似于承诺的机制,名为推迟( deferred )。此外,承诺甚至会从 ECMAScript 6 的生成器中受益更多,也即是:能够编写看起来像是同步风格但却异步执行的代码

如果读者想要更多了解JavaScript 中的全新原生承诺及其优点, Jake Archibald 的 HTML5Rocks 文章可以作为一份良好的上手读物。另外还有一份小型兼容代码(polyfill),针对较老版本的浏览器。 Domenic Denicola 针对承诺特性做过许多很棒的演讲,而且长期以来一直是承诺的拥护者。关于承诺的 API 规范可以查看其所基于的 the Promises/A+ proposal ,以及 Mozilla 开发者网络

查看英文原文: Promises: The New Async Standard in Browser JavaScript?

2014 年 5 月 05 日 08:141192
用户头像

发布了 256 篇内容, 共 50.3 次阅读, 收获喜欢 4 次。

关注

评论

发布
暂无评论
发现更多内容

架构师训练营1期第1周:架构方法 - 作业

piercebn

极客大学架构师训练营

展览工厂2020南京国际人工智能产品展览会

InfoQ_caf7dbb9aa8a

快讯2020第十三届南京国际智慧工地装备展览会

InfoQ_caf7dbb9aa8a

优选2020第十三届南京国际智慧停车展览会

InfoQ_caf7dbb9aa8a

zabbix 4.x一键部署脚本

edd

一次年轻代GC长暂停问题的解决与思考

AI乔治

Java 架构 JVM 优化 GC调优

架构建模学习总结

林杭戴

极客大学架构师训练营

一文详解分布式缓存(附代码)

架构师修行之路

缓存 分布式 分布式缓存

一代巨星的陨落!

小齐本齐

程序员 程序人生 职场

【API进阶之路】做OCR文字识别,谁说必须要有AI工程师?

华为云开发者社区

API 文字识别 OCR

快讯2020第十三届南京国际大数据产业博览会

InfoQ_caf7dbb9aa8a

亚洲2020第十三届南京国际智慧新零售暨无人售货展览会

InfoQ_caf7dbb9aa8a

架构师训练营技术知识点

devfan

Golang领域模型-依赖倒置

奔奔奔跑

golang 架构 微服务 领域驱动设计 DDD

英特尔揭示智能边缘重大机遇,助推产业智能变革

intel001

StringBuilder 比 String 快?空嘴白牙的,证据呢!

小傅哥

小傅哥 string 面试题 StringBuilder StringBuffer

架构师训练营第 1 期-第一周命题作业

arthur

What’s New in Dubbo-go-hessian2 v1.7.0

apache/dubbo-go

golang 服务端 dubbo-go

java安全编码指南之:堆污染Heap pollution

程序那些事

Java java安全编码 java安全编码指南 堆污染

架构师训练营第一周学习总结

尹斌

极客大学架构师训练营

甲方日常 17

句子

生活 随笔杂谈

2020南京国际工业互联网及工业通讯展览会

InfoQ_caf7dbb9aa8a

大作业 一

Jaye

架构师训练营大作业

路易斯李李李

大作业 二

Jaye

架构师培训大作业二——知识思维导图

chanson

Spring 5 中文解析数据存储篇-事务同步和声明式事物管理

青年IT男

Spring5

架构师训练营1期 - 第一周 - 食堂就餐卡系统设计

三板斧

极客大学架构师训练营

架构师训练营1期 -- 第一周作业

曾彪彪

极客大学架构师训练营

Week15

一叶知秋

架构知识总结

飞雪

华为云 TechWave 全球技术峰会

华为云 TechWave 全球技术峰会

承诺特性:在浏览器中使用JavaScript进行异步工作的新标准?-InfoQ