硬核干货——《中小企业 AI 实战指南》免费下载! 了解详情
写点什么

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

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

关注

评论

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

【HarmonyOS 5】App Linking 应用间跳转详解

深海的鲸同学 luvi

鸿蒙 HarmonyOS HarmonyOS NEXT 实践分享

数据中心里的AI:从幕后工具到智能大脑的蜕变

ScaleFlux

原生 iOS App 开发的优势

北京木奇移动技术有限公司

软件外包公司 APP外包 APP开发公司

Mybatis-Plus 集成 YashanDB 时分页功能怎么配置?

数据库砖家

数据库

原生 Android App 开发的优势

北京木奇移动技术有限公司

软件外包公司 APP外包公司 APP开发公司

为开源鸿蒙开发者而生,开鸿Bot系列今日预售启动

科技热闻

YashanDB|使用触发器复制 varchar(4000 char) 数据出现乱码问题的处理方案

数据库砖家

数据库

如何检测网站是否支持IPv6?有哪些指标?

国科云

KET口语考试APP的开发

北京木奇移动技术有限公司

软件外包公司 APP外包公司 KET口语考试

旧貌换新颜:闪迪创作者系列助力修复师用视频为古书画按下‘重启键’

极客天地

京东拍立淘图片搜索 API 接入实践:从图像识别到商品匹配的技术实现

tbapi

京东API 京东图片搜索接口 京东拍立淘API 京东图片API

YashanDB 单机部署时报错:prohibited operation?

数据库砖家

数据库

YashanDB 如何限制用户连接数?三步搞定!

数据库砖家

数据库

联想携手ISV“行业私厨”,让智能体落地实践端上行业“餐桌”

脑极体

AI

高性能、高可用的 Lustre 解决方案:使用 xiRAID 4.1 在双节点共享 NVMe 环境下

Sergey Platonov

性能测试 Lustre 高可用性 安装指南

Android App 的上线流程

北京木奇移动技术有限公司

软件外包公司 APP外包公司 APP开发公司

捷报!天翼云CTyunOS系统入选中央国家机关采购名单!

天翼云开发者社区

操作系统 天翼云 CTyunOS系统

《算法导论(第4版)》阅读笔记:p59-p75

codists

算法

YashanDB|使用 leading hint 优化 SQL 报 YAS-04522 错误的应对方法

数据库砖家

数据库

BOM都没整明白,就别抱怨生产一团糟了!

积木链小链

数字化转型 BOM 智能制造 生产管理

【YashanDB 知识库】Mybatis-Plus 如何正确配置分页?Oracle 和 MySQL 写法都能用!

数据库砖家

数据库

YashanDB 启动报错:control file 版本不兼容,怎么处理?

数据库砖家

数据库

YashanDB 部署报错:YAS-05721 节点名非法怎么办?

数据库砖家

数据库

YashanDB|YMP 报错“OCI 版本为空或架构不一致”?一文快速排查

数据库砖家

数据库

SDP架构在零信任中的实践

天翼云开发者社区

安全 零信任 sdp

Blender 入门教程(三):骨骼绑定

北桥苏

建模 blender

HyperMesh视觉控制

智造软件

仿真软件 Hypermesh hyperworks

天翼云助力中国交建,引领交通基建迈入数智新时代!

天翼云开发者社区

交通 大模型 数智化转型

YashanDB 报错 YAS-07301:通过 dblink 查询 Oracle 超时?可能是系统资源瓶颈

数据库砖家

数据库

YashanDB 使用 leading hint 报错 YAS-04522?当前版本不支持该写法

数据库砖家

数据库

CodeBuddy暴改漏洞实录

VyrnSynx

CodeBuddy首席试玩官

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