AICon上海|与字节、阿里、腾讯等企业共同探索Agent 时代的落地应用 了解详情
写点什么

承诺特性:在浏览器中使用 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:141814
用户头像

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

关注

评论

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

敏捷开发专家一席谈:云原生技术下的华为云DevOps实践之路

华为云开发者联盟

DevOps 敏捷开发 华为云 devcloud

Flink CDC 2.0 数据处理流程全面解析

大数据技术指南

11月日更

TDSQL | 多类型数据库统一管理,腾讯云数据库DBhouse工具重磅发布

腾讯云数据库

数据库 tdsql

openLooKeng v1.4.1 上线,OmniData Connector 来了

LooK

大数据 openLooKeng

如何“拼”出一个页面-游戏中心模块化实践

vivo互联网技术

开发 组件化 产品方法论

CodeGuide 300+文档、100+代码库,一个指导程序员写代码的,Github 仓库开源啦!

小傅哥

Java GitHub 小傅哥 开源社区 代码库

TDSQL首次登上腾讯财报!金融机构核心系统落地实现规模化复制

腾讯云数据库

数据库 tdsql

8大原则带你秒懂Happens-Before原则

华为云开发者联盟

线程 并发 Happens-Bfore Java内存

新机遇,拨开证劵企业生态转型迷雾

大咖说

云计算 阿里云 数字化转型 数字化 企业上云

用明道云集成多平台多部门数据,发挥数据分析的力量

明道云

新时代下如何构建TDSQL-C数据库产品

腾讯云数据库

数据库 tdsql

题目三、简述HDFS的读写流程

arctec

设计消息队列存储消息数据的MySQL表格

毛先生

MySQL打印死锁日志

Simon

MySQL 死锁

【Flutter 专题】08 图解页面小跳转 (一)

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 11月日更

创业自研技术团队筹建需要注意什么?

boshi

创业 团队管理

体验内容更新,MindSpore轻松掌握

Geek_6cdeb6

用vscode创建第一个flutter项目

坚果

flutter 11月日更

技术架构+应用场景揭秘,为什么高斯Redis比开源香?

华为云开发者联盟

数据库 redis 开源 云原生 高斯Redis

毫米波 5G 比公共 Wifi 快近 30 倍,但覆盖范围相似

吴脑的键客

网络

CSS布局之display:flex

Augus

CSS 11月日更

作业三总结

Geek_1d37ea

架构训练营

10行代码,撸一个在线个人简历页面!

老表

Python GitHub Linux web开发 跟老表学云服务器

微信小程序开发:使用字体图标的方法

三掌柜

11月日更

懒人畅听网,有声小说类目数据采集,多线程速采案例,Python爬虫120例之23例

梦想橡皮擦

11月日更

大数据训练营学习总结

arctec

2021 年 Node.js 的发展趋势

CRMEB

如何在 JavaScript 中操作二维数组

devpoint

JavaScript array 数组操作 11月日更

模块三

DevOps为何在中国发展滞后?

飞算JavaAI开发助手

详细剖析Kafka架构及组件

五分钟学大数据

11月日更

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