写点什么

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

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

关注

评论

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

KubeMeet 报名 | 「边缘原生」线上技术沙龙完整议程公布!

阿里巴巴云原生

阿里云 容器 云原生 边缘计算

算力顶天地,存力纳乾坤:国家超级计算济南中心的一体两面

脑极体

Serverless实战——2分钟,教你用Serverless每天给女朋友自动发土味情话

Serverless Devs

#Serverless

经验分享|编写简单易用的在线产品手册小妙招

Baklib

2022中国物流产业大会暨企业家高峰论坛在杭州举办!

联营汇聚

人社部公布“数据库运行管理员”成新职业,OceanBase参与制定职业标准

OceanBase 数据库

在 Spring Boot 中使用 Dataway 配置数据查询接口

程序员啊叶

Java 编程 程序员 架构 java面试

SR-TE的功能架构概述

穿过生命散发芬芳

7月月更 SR-TE

浅谈智能家居应用及传输方式

家和万事兴

物联网,

直播实录 | 37 手游如何用 StarRocks 实现用户画像分析

StarRocks

数据库 大数据

NFTScan 与 PANews 联合发布多链 NFT 数据分析报告

NFT Research

区块链 以太坊 NFT

第三届云原生编程挑战赛正式启动,Serverless 赛道邀你参加!

Serverless Devs

面试官:MySQL如何根据执行计划调优SQL语句?

程序员小毕

Java MySQL 数据库 程序员 面试

2022年最全大厂面试真题解析:java集合+spring+并发编程+MyBatis

程序员啊叶

Java 编程 程序员 架构 java面试

传统渲染农场和云渲染农场选择哪个好?

Finovy Cloud

云渲染 GPU服务器

要卖课、要带货,知识付费系统帮你一步搞定!

CRMEB

腾讯开源摘星计划培养开源贡献者的实践思考

腾源会

开源 腾源会

纯css实现:单行文本的打字机动画效果

南极一块修炼千年的大冰块

7月月更

知识库对企业的意义

Baklib

详解异步任务 | 看 Serverless Task 如何解决任务调度&可观测性中的问题

Serverless Devs

云原生

Apache Doris 1.1 特性揭秘:Flink 实时写入如何兼顾高吞吐和低延时

SelectDB

数据库 flink 数据仓库 Doris 数仓

面向大数据存算分离场景的数据湖加速方案

Baidu AICLOUD

数据湖 对象存储 元数据 存算分离 层级namespace

闭关三月:整理出了这份对标阿里p7的java秋招面试必备指南。

程序员啊叶

Java 编程 程序员 架构 java面试

怎么实现您的个人知识库?

Geek_da0866

SpringBoot 遗忘后的简单快速回忆之环境搭建与常见注解

程序员啊叶

Java 编程 程序员 架构 java面试

重磅来袭!豆瓣评分9.9,万人血书的多线程与高并发v2.0版本

冉然学Java

编程 源码 高并发 线程池 多线程并发

版本更新 | 极狐GitLab 15.2 发布飞书通知机器人、多层史诗调整至专业版、实时 Wiki 图表预览和全新设计的合并请求报告

极狐GitLab

git DevOps 敏捷开发 CI/CD 极狐GitLab

你想怎么使用 Serverless 函数计算?(评测赢好礼 )

Serverless Devs

活动回顾 | 大咖云集“开源安全治理模型和工具”线上研讨会

安势信息

开源安全 供应链攻击 SBOM SLSA 软件供应链安全

7 行代码搞崩溃 B 站,原因令人唏嘘!

Python猫

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