【AICon】探索RAG 技术在实际应用中遇到的挑战及应对策略!AICon精华内容已上线73%>>> 了解详情
写点什么

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

  • 2014-05-05
  • 本文字数:2867 字

    阅读完需:约 9 分钟

每个使用 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-05-05 08:141545
用户头像

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

关注

评论

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

9个常用ES6特性归纳(一般用这些就够了)

华为云开发者联盟

Java 程序员 编程语言 ES6 编程效率

获奖名单公布 | 写作平台八月宠粉福利来袭,参与创作领取限时大奖~

InfoQ写作社区官方

写作平台 征稿 热门活动

将设计模式应用到日常的curd中-模板方法和装饰器

LSJ

Java 设计 设计模式 装饰器 模板方法

week 10

Geek_2e7dd7

2.2.2 类反射场景与使用 -《SSM深入解析与项目实战》

谙忆

计算机网络基础(十七)---传输层-TCP的可靠传输

书旅

TCP 计算机网络 网络协议 计算机基础 TCP/IP

DT时代释放金融数据价值,驱动金融商业裂变

华为云开发者联盟

金融科技 华为云 modelarts 数据价值 用户细分

腾讯SaaS千帆对渠道的态度——合作共赢的诚意

人称T客

原创 | 使用JPA实现DDD持久化- O:对象的世界(3/3)

编程道与术

Java hibernate DDD JDBC jpa

Github+docsify零成本轻松打造在线文档网站

Java全栈封神

Java GitHub 全栈 文档 docsigy

英特尔推出“OpenVINO领航者联盟”,携手DFRobot推进AI商业落地新探

最新动态

为什么 90 后一言不合就跳槽?

非著名程序员

程序员 个人成长 职业成长 延迟满足感 即时反馈

AndroidStudio解决Unknown host 'd29vzk4ow07wi7.cloudfront.net'. You may need to adjust the proxy settings in Gradle

小菜鸟学php

第四届IMC再起烽烟 极致性能助战力升级!

最新动态

你是否真的懂数组?

架构师修行之路

数组 数据结构与算法

Linux神器strace的使用方法及实践

华为云开发者联盟

Linux 运维 工具 后端 Strace

IMC御用设备到底有多强?英特尔携手掠夺者呈现“飞”一般5GHz电竞盛宴

最新动态

支持 100 种语言的 Canva 是怎么做本地化管理的?

葛仲君

产品经理 本地化 产品本地化 国际化

丢弃掉那些BeanUtils工具类吧,MapStruct真香!!!

Hollis

Java 程序员 后端

不断壮大的电竞生态——英特尔大师挑战赛携手李宁中国选手等你来战!

最新动态

微服务的认识

chenzt

week 10

Geek_2e7dd7

十五张图带你彻底搞懂从URL到页面展示发生的故事

执鸢者

大前端 浏览器 页面展示

5招详解linux之openEuler /centos7防火墙基本使用指南

华为云开发者联盟

centos7 网络安全 防火墙 openEuler 网络环境

LeetCode题解:21. 合并两个有序链表,迭代(优化),JavaScript,详细注释

Lee Chen

大前端 LeetCode

微服务架构

不在调上

最右JS2Flutter框架——动画、小游戏的实现(四)

刘剑

flutter 大前端 探索与实践

华为阿里下班时间曝光:所有的光鲜,都有加班的味道

程序员生活志

华为 加班 阿里

论做AI芯片的正确姿势

flow

十二张图搞懂浏览器安全——(同源策略、XSS、CSRF、跨域、HTTPS、安全沙箱等知识点)

执鸢者

https 浏览器安全 同源策略 XSS 跨域

【权限系统设计】ACL, DAC, MAC, RBAC, ABAC模型的不同应用场景

小隐乐乐

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