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

本文对比了ECMAScript2015的异步编程和Binding.scala的FutureBinding技术,展示了如何用更简洁的方式实现从前端请求数据并展示的过程。

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 用户名,在文本框下方就会显示用户名对应的头像,如下图所示。

\\

4f54f2c3f9628de97013d107cbf23406.png

\\

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

\\

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

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

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

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

二、异步编程和 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 =\u0026gt; {\      if (this.currentPromise != currentPromise) {\        return;\      }\      if (response.status \u0026gt;= 200 \u0026amp;\u0026amp; response.status \u0026lt; 300) {\        return response.json();\      } else {\        this.currentPromise = null;\        this.setState({\          isLoading: false,\          error: response.statusText\        });\      }\    }).then(json =\u0026gt; {\      if (this.currentPromise != currentPromise) {\        return;\      }\      this.currentPromise = null;\      this.setState({\        isLoading: false,\        avatarUrl: json.avatar_url,\        error: null\      });\    }).catch(error =\u0026gt; {\      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 =\u0026gt; {\    const githubUserName = event.currentTarget.value;\    if (githubUserName) {\      this.sendRequest(githubUserName);\    } else {\      this.setState({\        githubUserName: githubUserName,\        isLoading: false,\        error: null,\        avatarUrl: null\      });\    }\  };\  render() {\    return (\      \u0026lt;div\u0026gt;\        \u0026lt;input type=\"text\" onChange={this.changeHandler}/\u0026gt;\        \u0026lt;hr/\u0026gt;\        \u0026lt;div\u0026gt;\          {\            (() =\u0026gt; {\              if (this.state.githubUserName) {\                if (this.state.isLoading) {\                  return \u0026lt;div\u0026gt;{`Loading the avatar for ${this.state.githubUserName}`}\u0026lt;/div\u0026gt;\                } else {\                  const error = this.state.error;\                  if (error) {\                    return \u0026lt;div\u0026gt;{error.toString()}\u0026lt;/div\u0026gt;;\                  } else {\                    return \u0026lt;img src={this.state.avatarUrl}/\u0026gt;;\                  }\                }\              } else {\                return \u0026lt;div\u0026gt;Please input your Github user name\u0026lt;/div\u0026gt;;\              }\            })()\          }\        \u0026lt;/div\u0026gt;\      \u0026lt;/div\u0026gt;\    );\  }\}
\\

一共用了 100 行代码。

\\

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

\\

三、Binding.scala

\\

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

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

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

四、结论

\\

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

\\
技术栈ReactJS + Promise + fetchBinding.scala
编程范式MVVM + 异步编程远程数据绑定
如何管理数据加载流程程序员手动编写异步编程代码自动处理
能不能用代码直接描述验收标准不能
从RESTful API加载数据并显示所需代码行数100行25行

这五篇文章介绍了用 ReactJS 实现复杂交互的前端项目的几个难点,以及 Binding.scala 如何解决这些难点,包括:

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

除了上述四个方面以外,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-as3Stateless Futurehaxe-continuationFastringEachMicrobuilderBinding.scala 。杨博曾在网易任主程序和项目经理,开发过多款游戏。现在ThoughtWorks任Lead Consultant,为客户提供移动、互联网、大数据、人工智能和深度学习领域的解决方案。

\\

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值