Why Callback/Thunk Sucks and Promise Rocks
- Dominant language
- HTML
- Stars
- 464
- Forks
- 27
- PR merge metrics
- No merged PRs in 30d
Description
## Callback 在大型编程时的一般性问题
### Callback hell 就不谈了
### 无法区分异步任务 callback(针对单一结果的处理) 和 listener(针对一类结果的处理)
例子:[fs.watch](http://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener) 在fs包中是特殊的
调用时是无法区分的,所以需要api文档说明。但是文档往往缺乏此说明。
### 无法区分 sync callback 和 async callback
请阅读:[Designing APIs for Asynchrony](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony) 或 [short version](https://github.com/oren/oren.github.io/blob/master/posts/zalgo.md)
调用时是无法区分的,所以需要api文档说明。但是文档往往缺乏此说明,且api设计者也很可能忽略这点而release zalgo。
## Node callback convention 的问题
node callback 相关的签名是基于约定的:
``` js
function asyncCall(...params, asyncCallback)
function asyncCallback(err, ...params)
```
基于约定导致:
### 约定会有例外
- [fs.exists](http://nodejs.org/api/fs.html#fs_fs_exists_path_callback) 的 callback 签名是 `function (boolean)`
- [Timers](http://nodejs.org/api/timers.html) 的 callback 参数位置是在第一个而不是最后一个
实际上,Timers的情况是一个普遍问题。Timers来自于Web Platform API,而 Web API 不可能遵循 Node callback 约定,所以一切使用 Web API 或需要保持和 Web API 兼容的,都面临约定不一致问题。
### err 参数应该是 Error 对象
参见:[A String is not an Error](http://www.devthought.com/2011/12/22/a-string-is-not-an-error/)
尽管此问题与callback本身并无必然关系(你也可以直接 throw 'string' ),但是 callback 形式更容易忽视。例子:[async.js的代码示例](https://github.com/caolan/async#user-content-eacharr-iterator-callback)
callback 也更难 enforce 此规则。比较:
``` js
function f(x) {
if (invalid(x)) throw 'invalid x'
...
}
function f(x, callback) {
if (invalid(x)) callback('invalid x')
...
}
```
对于前者,我们可以检查所有 throw 子句。而后者就困难多了。
特别注意,引擎其实可以对throw进行特殊处理,比如Chrome就是如此。
### 更容易 release Zalgo
前一个代码示例表明在做参数检查时很容易就同步调用callback从而破坏了前述async/sync区分的必要性。
## [thunks](https://github.com/thunks/thunks) 的问题
### 具有前述所有问题(除了callback hell)
### 形式极简的API未必是好的
- API形式的简单其实把复杂性留到了别处
- API命名和概念上的不清晰:
- thunk([一般概念](http://en.wikipedia.org/wiki/Thunk) VS 特定所指——参数为node callback约定形式的函数)
- thunks(包的export,实际是提供类似Domain的机制)
- Thunk(构造器)
- 链式调用返回thunk(接受的参数是node风格的回调,但是返回值处理有特例)
- 构造器的签名和后续调用实际是不同的
- 人类需要冗余信息来辅助阅读
### 微妙的语义差异
- 不像then可以被调用多次
- 如果thunk调用返回一个函数会被当成thunk,但是实际上无法区分这里需要thunk还是真的希望返回一个函数(实际上既和CPS风格不一致,又破坏函数式编程)
### 当前实现就是 releasing Zalgo 的!
而当前thunks的所谓性能优势(其实跟bluebird/when/rsvp等典型promise实现比完全没有优势)其实也来自于尽量同步执行,代价就是 releasing Zalgo。
由于实现方式的问题,当调用链过长时会 Maximum call stack size exceeded 。而原生Promise和所有主要的 Promise/A 实现都没有这个问题。
## Thenjs的问题
### 名同实异
- then 方法和 Promise 的 then 不一样
### 实现方式问题
- 和 thunks 一样也有 call stack size 问题
## Promise 的特点
### 语义清晰
- 一定只针对单一结果
- 必然是异步
- 基于明确的API,而不是基于约定
- 不需要语言外的错误处理机制
### 标准
- 基于社区共识,大部分开发者对它有一致的认知,有充分的实践,也经过编程语言专家的充分研究和探讨而定型
- 性能不是问题——现有的Promise/A的主流实现性能非常非常好,而native实现早晚会得到引擎的充分优化
- 更好的错误处理——如chrome开发版中已经把reject和throw一样可以trace普通值
- 未来语言设施(ES6 loader、ES 7 async/await 等)和新API(ServiceWorker、Stream等)的相容性
### 不足
- Cancelable (ES8?)
- Observable (ES7)
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.