原文 http://lucasmreis.github.io/b...javascript
Communicating Sequential Processes 的 7 个示例前端
CSP 是什么? 通常来讲, 它是写并行代码的一套方案.
在 Go 语言里自带该功能, Clojure 经过基于 Macro 的 core.async
来实现,
如今 JavaScript 经过 Generator 也能作支持了, 或者说 ES6 的功能.java
为何我要关心 CSP? 由于它强大啊, 并且高效, 并且简单. 都这样了你还想要什么? :)git
好吧, 说细节. 怎样使用呢?咱们用 js-csp
, 并且须要 generator 支持, ES6 才有.
也就说 Node 4 或者更高的版本才行, 或者浏览器代码用 Babel 编译一下,
固然能其余的编译工具可能也行, 但你要确认下是支持 Generator 的.github
注: 文章写得早, 如今翻译文章, Chrome 应该是支持 Generator 的.编程
扯多了, 来看例子吧!浏览器
第一个要学的概念是"进程". 进程能够执行代码, 简单说就是这样的了. :)缓存
注: 固然不是操做系统原始的进程了, js 里模拟的.前端框架
这是启动进程的语法: generator 函数做为参数, 传给 go
函数执行.babel
import {go} from 'js-csp'; go(function* () { console.log('something!'); }); // terminal output: // // => something!
使用 yield
关键字能够暂停一个进程, 把当前进程的占用释放:
import {go, timeout} from 'js-csp'; go(function* () { yield timeout(1000); console.log('something else after 1 second!'); }); console.log('something!'); // terminal output: // // => something! // => something else after 1 second!
第二个要学的概念是管道, 也是最后一个了. 管道就像是队列.
一旦进程对管道调用 take
, 进程就会暂停, 直到别人往管道放进数据.
import {go, chan, take, putAsync} from 'js-csp'; let ch = chan(); go(function* () { const received = yield take(ch); console.log('RECEIVED:', received); }); const text = 'something'; console.log('SENDING:', text); // use putAsync to put a value in a // channel from outside a process putAsync(ch, text); // terminal output: // // => SENDING: something // => RECEIVED: something
管道的另外一边, 往管道里 put
数据的那些进程也会暂停, 直到这边进程调用 take
.
下面的例子就复杂一点了, 试着跟随一下主线, 印证一下终端输出的内容:
import {go, chan, take, put} from 'js-csp'; let chA = chan(); let chB = chan(); // Process A go(function* () { const receivedFirst = yield take(chA); console.log('A > RECEIVED:', receivedFirst); const sending = 'cat'; console.log('A > SENDING:', sending); yield put(chB, sending); const receivedSecond = yield take(chA); console.log('A > RECEIVED:', receivedSecond); }); // Process B go(function* () { const sendingFirst = 'dog'; console.log('B > SENDING:', sendingFirst); yield put(chA, sendingFirst); const received = yield take(chB); console.log('B > RECEIVED:', received); const sendingSecond = 'another dog'; console.log('B > SENDING:', sendingSecond); yield put(chA, sendingSecond); }); // terminal output: // // => B > SENDING: dog // => A > RECEIVED: dog // => A > SENDING: cat // => B > RECEIVED: cat // => B > SENDING: another dog // => A > RECEIVED: another dog
因为管道是队列, 当进程从管道取走数据, 其余进程就拿不到了.
因此推数据的是一个进程, 取数据的也是一个进程.
下面这个例子能够看到第二个进程永远不会打印 B > RECEIVED: dog
,
由于第一个进程已经把数据取走了.
import {go, chan, take, put} from 'js-csp'; let ch = chan(); go(function* () { const text = yield take(ch); console.log('A > RECEIVED:', text); }); go(function* () { const text = yield take(ch); console.log('B > RECEIVED:', text); }); go(function* () { const text = 'dog' console.log('C > SENDING:', text); yield put(ch, text); }); // terminal output: // // => C > SENDING: dog // => A > RECEIVED: dog
put
操做时阻塞管道能够带缓冲, 也就是, 必定数量以内的数据, 执行 put
操做能够避开阻塞.
这个例子里, 即使没有其余进程调用 take
, 前两个写操做也不会阻塞进程.
不过管道的缓存数量是 2, 因此第三个数据就阻塞进程了, 直到其余进程取走数据.
import {go, chan, put, buffers} from 'js-csp'; let ch = chan(buffers.fixed(2)); go(function* () { yield put(ch, 'value A'); yield put(ch, 'value B'); console.log('I should print!'); yield put(ch, 'value C'); console.log('I should not print!'); }); // terminal output: // // => I should print!
固定大小的缓冲在 N 个数据以后会阻塞, 初次以外, 还有对缓冲的 dropping 和 sliding 控制.
缓冲的 dropping 觉得着管道能够持有 N 个数据.
再增长额外的数据放进管道, 管道就会将其丢弃.
缓冲的 sliding 也能够持有 N 个数据. 不过相对于直接丢弃新数据,
sliding 缓冲原先的第一个推的数据会被丢弃, buffer 里会留下新的这个数据.
下面这个例子, value B
和 value C
在 dropping 缓冲里被丢弃, 由于已经有 value A
了.
第二个进程里, 当 value B
被放进管道, value A
就被丢弃了.
而后 value C
放进管道, value B
就被丢弃.
根据它们的工做原理, dropping 和 sliding 的缓冲永远不会阻塞!
let droppingCh = chan(buffers.dropping(1)); let slidingCh = chan(buffers.sliding(1)); go(function* () { yield put(droppingCh, 'value A'); yield put(droppingCh, 'value B'); yield put(droppingCh, 'value C'); console.log('DROPPING:', yield take(droppingCh)); }); go(function* () { yield put(slidingCh, 'value A'); yield put(slidingCh, 'value B'); yield put(slidingCh, 'value C'); console.log('SLIDING:', yield take(slidingCh)); }); // terminal output: // // => DROPPING: value A // => SLIDING: value C
CSP 用了一段时间以后, 用回调或者 Promise 写代码就像是侏罗纪的技术.
我但愿 ES6 的 Generator 能帮助 CSP 成为 JavaScript 的一个标准,
就像是 Go 已是的那样, 以及 Clojure 里正在成为的那样.
另外有两个模型也还有意思, 大概能够认为是比 CSP 层级更高一点的:
函数式也是响应式编程(Rx)跟 Actors, 分别在 Rx 和 Erlang 里用到.
我固然后面也会写博客来挖掘一下.
我同时相信 CSP 对于前端框架来讲很是棒.
原做者还有一个文章能够看下: Using CSP as Application Architecture