【QCon】精华内容上线92%,全面覆盖“人工智能+”的典型案例!>>> 了解详情
写点什么

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:587753

评论

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

如何借助低代码开发平台 YonBuilder 填补应用开发 “产能缺口”?

YonBuilder低代码开发平台

开发者 低代码

AngularJS进阶(十三)JS利用正则表达式校验手机号

No Silver Bullet

正则表达式 AngularJS 12月月更

守护安全|AIRIOT城市天然气综合管理解决方案

AIRIOT

物联网 天然气

排查Linux恶意进程

灵霄

建筑中如何使用3D可视化?

3DCAT实时渲染

云计算 3D可视化 智慧建筑 BIM

这才是Git的正确学习方式!

Jackpop

观测云产品更新|应用性能新增服务清单功能;用户访问监测 Session 查看器调整;事件新增移动端跳转选项等

观测云

可观测性 观测云

升维数智化底座 迈向高质量发展

用友BIP

云原生中的标准化

穿过生命散发芬芳

云原生 12月月更

Fuzzing(模糊测试)的前世今生(下)

云起无垠

网络安全 漏洞挖掘 Fuzzing 模糊测试

技术分享 | 将覆盖反馈融入黑盒模糊测试技术提升测试效率

云起无垠

Fuzzing(模糊测试)的前世今生(上)

云起无垠

Fuzzing(模糊测试)的前世今生(中)

云起无垠

实时渲染正在改变可视化游戏

3DCAT实时渲染

云计算 云服务器 云算力 渲染引擎 渲染

如何远程Debug内网(或者防火墙后)的Java服务

石臻臻的杂货铺

Java debug 后端

Python初学者必备!适合新手阅读的Github开源代码。。。

Jackpop

33K Star!这才是程序员需要的神器。。。

Jackpop

子查询优化之 Semi-join 优化 | StoneDB 研发分享 #2

StoneDB

MySQL HTAP 数据库· StoneDB 12 月 PK 榜

面对当下最热的多模态,为什么这些业界和学界专家说“不必追热点”

小红书技术REDtech

AngularJS进阶(十一)AngularJS实现表格数据的编辑,更新和删除

No Silver Bullet

表格 AngularJS 12月月更

直播 | 数据仓库?数据湖?停止纠结,流批融合的极速 Lakehouse来了!

StarRocks

#数据库

Proxyless Mesh 在 Dubbo 中的实践

阿里巴巴中间件

阿里云 云原生 dubbo

【愚公系列】2022年12月 微信小程序-Component组件

愚公搬代码

12月月更

Fuzzing(模糊测试)技术,你真的了解吗?

云起无垠

2022-12-02:有a块草莓蛋糕,有b块芝士蛋糕,两人轮流拿蛋糕, 每次不管是谁只能选择在草莓蛋糕和芝士蛋糕中拿一种, 拿的数量在1~m之间随意, 谁先拿完最后的蛋糕谁赢。 返回先手赢还是后手赢。

福大大架构师每日一题

算法 rust 福大大

太简单了,一文彻底搞懂Jenkins的用法!

Jackpop

ElasticSearch 低成本高可用最佳实践

冰心的小屋

elasticsearch 高可用性

Python初学者必备!适合新手阅读的Github开源代码。。。

Jackpop

焱融全闪系列科普| 为什么 SSD 需要 NVMe?

焱融科技

云计算 高性能 nvme 文件存储 全闪存储

AngularJS进阶(十)AngularJS改变元素显示状态

No Silver Bullet

AngularJS 12月月更 元素状态

如何选择正确的哈希算法?

Jackpop

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