阿里云「飞天发布时刻」2024来啦!新产品、新特性、新能力、新方案,等你来探~ 了解详情
写点什么

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

评论

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

华为初面+综合面试(Java技术面)附上面试题,share给大家~

Java 编程 程序员 面试

jodconverter实现在线预览

小鲍侃java

11月日更

墨天轮国产数据库沙龙 | 胡彦军:华为GaussDB迁移工具解密

墨天轮

数据库 华为云 GaussDB 国产数据库

2022第十五届北京国际智慧城市、物联网、大数据博览会

InfoQ_caf7dbb9aa8a

【云图说】DRS数据对比——带您随时观测数据一致性

华为云数据库小助手

GaussDB 华为云数据库 华为云DRS

Python Qt GUI设计:信号与槽的使用方法(基础篇—7)

不脱发的程序猿

Python qt PyQt GUI

NodeJs 深入浅出之旅:V8 内存分配🧙‍♂️

空城机

大前端 Node 11月日更

在线问诊系统功能以及快速发展的意义

风行无疆

(文末福利)云上论剑,谈谈如何构建新的数据系统技术体系

Zilliz

数据库

恒源云(GPUSHARE)_云GPU服务器如何使用 TensorBoard?

恒源云

深度学习

浏览器的几种防护策略

网络安全学海

网络安全 信息安全 渗透测试 WEB安全 安全漏洞

识别AI换脸!百度这项技术夺冠了!

百度开发者中心

AI

恒源云(GPUSHARE)_云GPU服务器如何使用 JupyterLab?

恒源云

深度学习

gitlab registry占用存储过大问题解决

ilinux

直接破防了,阿里大咖DDD(领域驱动设计)不破不立,GitHub直接霸榜,今天share给大家~

编程 程序员 领域驱动

手写自定义迭代器,秒懂迭代器底层原理

Tom弹架构

Java 架构 设计模式

Forrester发布「2021年低代码平台中国市场现状分析报告」,钉钉宜搭入选

一只大光圈

低代码 数字化转型 低代码开发 低代码平台 钉钉宜搭

何止一个惨字形容,水滴Java面试一轮游,壮烈了,问啥啥不会,数据库血崩,我该怎么办?

Java 编程 程序员 面试

1 分钟学会 30 种编程语言

AlwaysBeta

数据基础设施支撑电力人工智能:新能源集控智能管理

EMQ映云科技

人工智能 物联网 电力

SphereEx 中文开源社区正式开站!精美礼品等你来拿

SphereEx

开源社区 ShardingSphere SphereEx Apache ShardingSphere 中文开源

同城本地生活信息服务软件开发你知道多少?

风行无疆

全能文件恢复软件推荐

淋雨

数据恢复

浅谈网络性能之端到端业务质量分析

鲸品堂

运营商

成为弹唱高手的秘诀!看这一篇就足够!

懒得勤快

编解码再进化:Ali266 与下一代视频技术

阿里云视频云

阿里云 音视频 视频编码 视频编解码 视频云

勒索软件即服务与IAB产业浅析

腾讯安全云鼎实验室

安全攻防 勒索病毒

Flink Sort-Shuffle 实现简介

Apache Flink

大数据 flink 实时计算

自定义View:如何实现双击点放大图片控件

Changing Lin

11月日更

linux下清理系统缓存并释放内存

入门小站

Linux

CODING Compass —— 打造行云流水般的软件工厂

CODING DevOps

DevOps 研发管理工具 流程化

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