阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

More than React(五)异步编程真的好吗?

  • 2017-01-26
  • 本文字数:4630 字

    阅读完需:约 15 分钟

More than React 系列的上一篇文章《HTML 也可以编译?》介绍了 Binding.scala 如何在渲染 HTML 时静态检查语法错误和语义错误,从而避免 bug ,写出更健壮的代码。本篇文章将讨论Binding.scala 和其他前端框架如何向服务器发送请求并在页面显示。

在过去的前端开发中,向服务器请求数据需要使用异步编程技术。异步编程的概念很简单,指在进行 I/O 操作时,不阻塞当前执行流,而通过回调函数处理 I/O 的结果。不幸的是,这个概念虽然简单,但用起来很麻烦,如果错用会导致 bug 丛生,就算小心翼翼的处理各种异步事件,也会导致程序变得复杂、更难维护。

Binding.scala 可以用 I/O 状态的绑定代替异步编程,从而让程序又简单又好读,对业务人员也更友好。

我将以一个从 Github 加载头像的 DEMO 页面为例,说明为什么异步编程会导致代码变复杂,以及 Binding.scala 如何解决这个问题。

一、DEMO 功能需求

作为 DEMO 使用者,打开页面后会看到一个文本框。

在文本框中输入任意 Github 用户名,在文本框下方就会显示用户名对应的头像,如下图所示。

要想实现这个需求,可以用 Github API 发送获取用户信息的 HTTPS 请求。

发送请求并渲染头像的完整流程的验收标准如下:

  • 如果用户名为空,显示“请输入用户名”的提示文字;

  • 如果用户名非空,发起 Github API,并根据 API 结果显示不同的内容:

    • 如果尚未加载完,显示“正在加载”的提示信息;
    • 如果成功加载,把回应解析成 JSON,从中提取头像 URL 并显示;
    • 如果加载时出错,显示错误信息。

二、异步编程和 MVVM

过去,我们在前端开发中,会用异步编程来发送请求、获取数据。比如 ECMAScript 2015 的 Promise 和 HTML 5 的 fetch API。

而要想把这些数据渲染到网页上,我们过去的做法是用 MVVM 框架。在获取数据的过程中持续修改 View Model ,然后编写 View 把 View Model 渲染到页面上。这样一来,页面上就可以反映出加载过程的动态信息了。比如,ReactJS 的 state 就是 View Model,而 render 则是 View ,负责把 View Model 渲染到页面上。

用 ReactJS 和 Promise 的实现如下:

复制代码
class Page extends React.Component {
state = {
githubUserName: null,
isLoading: false,
error: null,
avatarUrl: null,
};
currentPromise = null;
sendRequest(githubUserName) {
const currentPromise = fetch(`https://api.github.com/users/${githubUserName}`);
this.currentPromise = currentPromise;
currentPromise.then(response => {
if (this.currentPromise != currentPromise) {
return;
}
if (response.status >= 200 && response.status < 300) {
return response.json();
} else {
this.currentPromise = null;
this.setState({
isLoading: false,
error: response.statusText
});
}
}).then(json => {
if (this.currentPromise != currentPromise) {
return;
}
this.currentPromise = null;
this.setState({
isLoading: false,
avatarUrl: json.avatar_url,
error: null
});
}).catch(error => {
if (this.currentPromise != currentPromise) {
return;
}
this.currentPromise = null;
this.setState({
isLoading: false,
error: error,
avatarUrl: null
});
});
this.setState({
githubUserName: githubUserName,
isLoading: true,
error: null,
avatarUrl: null
});
}
changeHandler = event => {
const githubUserName = event.currentTarget.value;
if (githubUserName) {
this.sendRequest(githubUserName);
} else {
this.setState({
githubUserName: githubUserName,
isLoading: false,
error: null,
avatarUrl: null
});
}
};
render() {
return (
<div>
<input type="text" onChange={this.changeHandler}/>
<hr/>
<div>
{
(() => {
if (this.state.githubUserName) {
if (this.state.isLoading) {
return <div>{`Loading the avatar for ${this.state.githubUserName}`}</div>
} else {
const error = this.state.error;
if (error) {
return <div>{error.toString()}</div>;
} else {
return <img src={this.state.avatarUrl}/>;
}
}
} else {
return <div>Please input your Github user name</div>;
}
})()
}
</div>
</div>
);
}
}

一共用了 100 行代码。

由于整套流程由若干个闭包构成,设置、访问状态的代码五零四散,所以调试起来很麻烦,我花了两个晚上才调通这 100 行代码。

三、Binding.scala

现在我们有了 Binding.scala ,由于 Binding.scala 支持自动远程数据绑定,可以这样写:

复制代码
@dom def render = {
val githubUserName = Var("")
def inputHandler = { event: Event => githubUserName := event.currentTarget.asInstanceOf[Input].value }
<div>
<input type="text" oninput={ inputHandler }/>
<hr/>
{
val name = githubUserName.bind
if (name == "") {
<div>Please input your Github user name</div>
} else {
val githubResult = FutureBinding(Ajax.get(s"https://api.github.com/users/${name}"))
githubResult.bind match {
case None =>
<div>Loading the avatar for { name }</div>
case Some(Success(response)) =>
val json = JSON.parse(response.responseText)
<img src={ json.avatar_url.toString }/>
case Some(Failure(exception)) =>
<div>{ exception.toString }</div>
}
}
}
</div>
}

一共 25 行代码。

完整的 DEMO 请访问 ScalaFiddle

之所以这么简单,是因为 Binding.scala 可以用 FutureBinding 把 API 请求当成普通的绑定表达式使用,表示 API 请求的当前状态。

每个 FutureBinding 的状态有三种可能,None表示操作正在进行,Some(Success(...))表示操作成功,Some(Failure(...))表示操作失败。

还记得绑定表达式的 .bind 吗?它表示“each time it changes”。
由于 FutureBinding 也是 Binding 的子类型,所以我们就可以利用 .bind ,表达出“每当远端数据的状态改变”的语义。

结果就是,用 Binding.scala 时,我们编写的每一行代码都可以对应验收标准中的一句话,描述着业务规格,而非“异步流程”这样的技术细节。

让我们回顾一下验收标准,看看和源代码是怎么一一对应的:

  • 如果用户名为空,显示“请输入用户名”的提示文字;
复制代码
if (name == "") {
<div>Please input your Github user name</div>
  • 如果用户名非空,发起 Github API,并根据 API 结果显示不同的内容:
复制代码
} else {
val githubResult = FutureBinding(Ajax.get(s"https://api.github.com/users/${name}"))
githubResult.bind match {
  • 如果尚未加载完,显示“正在加载”的提示信息;
复制代码
case None =>
<div>Loading the avatar for { name }</div>
  • 如果成功加载,把回应解析成 JSON,从中提取头像 URL 并显示;
复制代码
case Some(Success(response)) =>
val json = JSON.parse(response.responseText)
<img src={ json.avatar_url.toString }/>
  • 如果加载时出错,显示错误信息。
复制代码
case Some(Failure(exception)) => // 如果加载时出错,
<div>{ exception.toString }</div> // 显示错误信息。

四、结论

本文对比了 ECMAScript 2015 的异步编程和 Binding.scala 的 FutureBinding 两种通信技术。Binding.scala 概念更少,功能更强,对业务更为友好。

技术栈 ReactJS + Promise + fetch Binding.scala 编程范式 MVVM + 异步编程 远程数据绑定 如何管理数据加载流程 程序员手动编写异步编程代码 自动处理 能不能用代码直接描述验收标准 不能 能 从 RESTful API 加载数据并显示所需代码行数 100 行 25 行这五篇文章介绍了用 ReactJS 实现复杂交互的前端项目的几个难点,以及 Binding.scala 如何解决这些难点,包括:

  • 复用性
  • 性能和精确性
  • HTML 模板
  • 异步编程

除了上述四个方面以外,ReactJS 的状态管理也是老大难问题,如果引入 Redux 或者 react-router 这样的第三方库来处理状态,会导致架构变复杂,分层变多,代码绕来绕去。而 Binding.scala 可以用和页面渲染一样的数据绑定机制描述复杂的状态,不需要任何第三方库,就能提供服务器通信、状态管理和网址分发的功能。

如果你正参与复杂的前端项目,使用 ReactJS 或其他开发框架时,感到痛苦不堪,你可以用 Binding.scala 一举解决这些问题。 Binding.scala 快速上手指南中包含了从零开始创建 Binding.scala 项目的每一步骤。

五、后记

Everybody’s Got to Learn How to Code
——奥巴马

编程语言是人和电脑对话的语言。对掌握编程语言的人来说,电脑就是他们大脑的延伸,也是他们身体的一部分。所以,不会编程的人就像是失去翅膀的天使。

电脑程序是很神奇的存在,它可以运行,会看、会听、会说话,就像生命一样。会编程的人就像在创造生命一样,干的是上帝的工作。

我有一个梦想,梦想编程可以像说话、写字一样的基础技能,被每个人都掌握。

如果网页设计师掌握 Binding.scala,他们不再需要找工程师实现他们的设计,而只需要在自己的设计稿原型上增加魔法符号.bind,就能创造出会动的网页。

如果 QA、BA 或产品经理掌握 Binding.scala,他们写下验收标准后,不再需要检查程序员干的活对不对,而可以把验收标准自动变成可以运转的功能。

我努力在 Binding.scala 的设计中消除不必要的技术细节,让人使用 Binding.scala 时,只需要关注他想传递给电脑的信息。

Binding.scala 是我朝着梦想迈进的小小产物。我希望它不光是前端工程师手中的利器,也能成为普通人迈入编程殿堂的踏脚石。

六、相关链接

七、More than React 系列文章

《More than React(一)为什么ReactJS 不适合复杂交互的前端项目?》

《More than React(二)组件对复用性有害?》

《More than React(三)虚拟DOM 已死?》

《More than React(四)HTML 也可以静态编译?》

《More than React(五)异步编程真的好吗?》

作者简介

杨博是 Haxe 和 Scala 社区的活跃贡献者,发起和维护的开源项目包括 protoc-gen-as3 Stateless Future haxe-continuation Fastring Each Microbuilder Binding.scala 。杨博曾在网易任主程序和项目经理,开发过多款游戏。现在 ThoughtWorks 任 Lead Consultant,为客户提供移动、互联网、大数据、人工智能和深度学习领域的解决方案。


感谢张凯峰对本文的策划,韩婷对本文的审校。

2017-01-26 01:587841

评论

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

强大的视频修复软件:Aiseesoft Video Repair免激活最新版

mac大玩家j

Mac软件 视频处理工具 视频修复软件 视频管理

源码补丁神器—patch-package

京东科技开发者

强大的音频格式转换工具:dBpoweramp Music Converter 激活最新

胖墩儿不胖y

Mac软件 音频格式转换器 音频格式管理

【堡垒机小问答】堡垒机最早起源于哪里?是国外吗?

行云管家

云计算 网络安全 堡垒机 云堡垒机

3D 建模中的法线贴图解释

3D建模设计

3D渲染 材质纹理贴图 3D材质编辑

3D游戏角色建模纹理贴图处理

3D建模设计

3D渲染 材质纹理贴图 3D材质编辑

某乳品龙头企业,以精细化费用管理降低企业费效比

用友BIP

降本增效

位移贴图、凹凸贴图和法线贴图之间的差异

3D建模设计

3D渲染 材质纹理贴图 3D材质编辑

“踩坑”经验分享:Swift语言落地实践

百度Geek说

ios swift 开发语言 企业号12月PK榜

Mac微软远程连接工具:Microsoft Remote Desktop for Mac中文破解版 支持M1

iMac小白

AI数字人主播成电商直播标配!

青否数字人

数字人

低代码搭建工程项目管理方案:实现高效智能的数字化管理

天津汇柏科技有限公司

低代码

无锡梁溪携手极视角共同举办2023无锡首届国际人工智能算法大赛

科技热闻

【鸿蒙千帆起】《钢岚》成为首款基于HarmonyOS NEXT开发的战棋新游

HarmonyOS开发者

HarmonyOS

SpamSieve for mac(邮件过滤工具) v3.0.3完整激活版

mac

苹果mac Windows软件 SpamSieve 电子邮件过滤软件

永久激活版MAMP PRO for Mac破解版

iMac小白

java进行数据库操作的并发控制的2种方法

华为云开发者联盟

Java 数据库 后端 华为云 华为云开发者联盟

Navicat Premium 15 for Mac中文破解版:设计数据库的利器

iMac小白

华为云开发者日,让开发者成为产业发展的决定性力量

华为云开发者联盟

开发者 华为云 华为云开发者联盟 华为云开发者日

PBR纹理贴图类型详解

3D建模设计

3D渲染 材质纹理贴图 3D材质编辑

Curve 如何演进 (1):从 EuroSys'23 CFS 论文看文件系统

OpenCurve

分布式 云原生 文件存储 分布式文件存储 分布式文件系统

Proxifier for mac破解版 密钥激活 支持M

iMac小白

大模型驱动的AI交互式数字人!

青否数字人

数字人

S3 调用次数减少 98% | 探索 OpenDAL RangeReader 的奥秘

Greptime 格睿科技

数据库 时序数据 OpenDAL

泛互联网行业A/B测试全解析:产品优化的创新之道

字节跳动数据平台

大数据 ab测试 企业号12月PK榜 对比试验 数字化增长

跨境通讯

ctsxiyou

通信 通讯

低代码技术特点揭秘:构建灵活安全的多租户数据生态

天津汇柏科技有限公司

低代码

法线贴图可以实现什么样的3D效果

3D建模设计

3D渲染 材质纹理贴图 3D材质编辑

不可避免,制造企业要在供应链“韧性”上下苦功

用友BIP

数智采购

MacOS数据库:Navicat Premium 15 for Mac中文破解版

iMac小白

TF-IDF算法是什么呢?

小齐写代码

More than React(五)异步编程真的好吗?_JavaScript_杨博_InfoQ精选文章