我开发了一个基于 Beancount 的账本托管服务 HostedBeans,欢迎大家来了解纯文本复式记账并试用我的服务。
归档 2015 年 4 月

Promise:抽象的异步任务

我现在才认识到,我在学习 Node.js 上走的最大的弯路就是很晚才开始了解和使用 Promise.

callback 风格的问题

Node.js 和其他主流语言最不同的地方就在于 Node.js 中所有的 IO 操作都是异步的,在传统的 callback 风格的代码中,异步函数和同步函数(非异步函数)是有很大的区别的,在使用时必须加以区分。

异步函数的最后一个参数是一个称作 callback 的函数,这个函数会在异步任务完成后被执行,第一个参数表示是否出现了错误,其余参数是异步任务执行的结果。而同步函数的执行结果作为返回值返回,在出错时会抛出异常。

try
  console.log syncTask input
catch err
  console.error err

asyncTask input, (err, output) ->
  if err
    console.error err
  else
    console.log output

JavaScript 的异常机制在同步的情况下工作良好,但在异步的情况下则需要你在每一个异步函数完成后手动检查错误,因为这些异步函数并不在同一个调用栈上运行,所以不会像异常一样逐级传递。

try
  input2 = syncTask1 input
  console.log syncTask2 input2
catch err
  console.error err

asyncTask1 input, (err, input2) ->
  if err
    console.error err
  else
    asyncTask2 input2, (err, output) ->
      if err
        console.error error err
      else
        console.log output

这种不一致使得我们需要花费额外的精力去关注异步任务和同步任务之间的差别。一个例子是当一个函数由同步改为异步(例如一个函数本来不需要进行 IO, 但后来变得需要了)的时候需要修改所有调用处,简直令人抓狂。

Promise 的解决方案

而 Promise 通过建立抽象的方式,消除了同步函数和异步函数在调用方法、结果返回、异常流程处理上的差别。

对于 Promise 风格的异步函数,我们不需要在调用时传递一个 callback, 而是像调用同步函数一样,只传递真正的参数。

syncTask input
asyncTask input

Promise 风格的函数的结果总是一个值 —— 因为一个函数只能有一个返回值。

我们总是通过函数返回的 Promise 对象来绑定 callback 或捕捉错误。

syncTask(input).then (output) ->
  console.log output
, (err) ->
  console.error err

asyncTask(input).then (output) ->
  console.log output
, (err) ->
  console.error err

在 Promise 风格的函数中,我们可以通过两种方式来产生一个错误:抛出异常或返回一个「rejected 的 Promise」。

task1 = ->
  throw new Error()

task2 = ->
  return Promise.reject new Error()

当出现错误时,错误会被逐级传递,我们只需在最后一步捕捉错误即可。

task1(input).then (input2) ->
  task2 input2
.then (input3) ->
  task3 input3
.then (output) ->
  console.log output
, (err) ->
  console.error err

Promise 的实现

从实现的角度来将,Promise 风格的函数总是返回一个 Promise 的实例,Promise 的实例是对「一项任务」的抽象。

任务可以有三种状态:正在进行(pending)、成功(resolved)、错误(rejected)。任务的结果(成功时)或错误(错误时)会被保存为这个对象的内部状态,外部只能通过 then 来与这项任务的结果进行交互。

当使用 then 来绑定回调函数时,如果这项任务已经完成,则直接使用内部保存的结果来通知回调函数;若这项任务还未完成,则将回调函数放入队列中,等待任务完成再进行通知。

这种设计实际上是一种订阅/通知模型,Promise 对象负责维护订阅关系,而任务和回调函数本身是不存在联系的。

Promise 的第三方拓展

Promises/A+ 标准仅为 Promise 实例规定了 then 方法,在实际应用中,我们会使用一些与 Promises/A+ 兼容但拓展了更多功能的 Promise 实现,例如:

Atom 中文社区!

我最近半年开始变成 Atom 的深度用户,每天用 Atom 完成我的工作,同时也在了解 Atom 的构造,阅读源代码,编写插件。可以说 Atom 的中文用户很少,我希望能够帮助其他的中文用户了解 Atom, 在经过一番权衡之后,最后还是决定自建一个社区。

http://atom-china.org/

目前我已经翻译了几篇官方的博客和手册:

以及我前些天自己尝试编写的一个插件:

Atom 安装包的百度盘镜像:

http://pan.baidu.com/s/1o6r8DOu (相关讨论)

PS: Atom 中文社区现已加入 中文技术社区联盟

Atom 体验报告

回想起来,大概是去年这个时候,Github 宣布开始开发 Atom, 当时 V2EX 上到处都有人在求邀请码。因为 Github 并没有严格地限制下载和使用,所以虽然我当时没有邀请码,但也试用了一下 Atom. 当时的感觉是性能非常差,Bug 非常多,非常难用,于是就放下了。

后来 2014 年末,大概是听说 Atom 要发布 1.0 版本了(虽然到现在仍未发布),于是又下载试用了一个月左右,发现已经很少遇到 Bug 了,于是彻底从 WebStorm 迁移到了 Atom, 后来几个月变成了 Atom 的脑残粉。

喜欢 Atom 并不是因为它已经有多么好用了,直到现在 Atom 也仍然存在很多问题,我更看好的是 Atom 的设计理念和它未来的发展。Atom 希望兼具易用性和可编程性,它希望既可以对第一天学习编程的学生展现出易用性,又可以对一个代码大师展现出强大的可编程性。

Atom 的第一个特点是它构建于 Web 技术之上,最近几年 Web 技术已经发展成了一种新的「操作系统」,具有一套完整的 API 来加速应用的开发,几乎颠覆了传统的应用开发模式。Atom 构建于 Chromium 和 Node.js 之上,Chromium 是最流行的浏览器之一,借助 Web 技术可以直接通过 HTML 来布局,用 CSS 来影响界面的样式,因为 Atom 使用内建的 Web 引擎,所以可以直接使用新的 Web 技术,不必顾及浏览器兼容性等琐碎细节。Node.js 则赋予了 JavaScript 使用 C++ 拓展、完整地访问网络和文件系统的能力,并且可以直接使用 NPM 上已有的丰富资源。

Atom 的第二个特点是它使用了一种完全插件化的设计,Atom 的核心仅仅是一个管理插件的框架(核心仅 15000 行代码),任何「有意义」的功能都被以插件的形式实现,实际上 Atom 内置了大概 70 个插件来实现诸如文件列表、设置面板、命令面板、查找替换之类的基本功能。作为一个通用的编辑器,不太可能直接面面俱到地考虑到各种需求,索性不如通过彻底地插件化来适应各种不同类型的开发任务。Atom 鼓励用户通过编程的方式去进行定制, Atom 具有设计良好的 API, 还有一份 非常美观的文档 被摆在了官网的显著位置。

最近一年我写 JavaScript 比较多,我发现对于 JavaScript 这种动态类型、动态作用域的语言,WebStorm 基于静态分析的自动补全费力不讨好,并不能准确地进行提示,索性不如直接用字符串模糊匹配的方式来进行自动补全,毕竟对于我这种重量级用户来说,是很清楚某个对象具有哪些属性的,补全只是为了加速。

和 Sublime Text 相比,Atom 的性能确实差了一截,大概和 WebStorm 差不多的样子。我觉得 Atom 在性能上还是有一些提升空间的,目前的卡顿主要是因为 JavaScript 是单线程的,遇到密集计算就会阻塞事件循环,至于解决方案自然就是把密集计算和 DOM 操作 分离成两个线程,但大概这项工作需要插件的开发者一同来配合,所以比较难以开展吧。

因为 Atom 以插件为中心,所以我也不好说它能做什么、不能做什么,总之目前 Atom 已经可以满足我的所有需求,Atom.io 为每个用户提供了一个用户页, 各位可以 在这里看到我使用的插件。在未来我希望 Atom 社区能出现更多好用的插件,将与开发相关的功能都集成进来,例如一个好用的终端、断点调试器、数据库查看器等等,我最近也在尝试自己编写插件,不过目前还没什么成果。

1

精子生于 1995 年,英文 ID jysperm.

订阅推送

通过 Telegram Channel 订阅我的博客日志、产品和项目的动态:

王子亭的博客 @ Telegram


通过邮件订阅订阅我的博客日志、产品和项目的动态(历史邮件):

该博客使用基于  Hexo  的  simpleblock  主题。博客内容使用  CC BY-NC-ND  授权发布。最后生成于 2023-12-20.