[譯] 快速介紹 JavaScript 中的 CSP

原文 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 的.編程

扯多了, 來看例子吧!瀏覽器

例 1: 進程

第一個要學的概念是"進程". 進程能夠執行代碼, 簡單說就是這樣的了. :)緩存

注: 固然不是操做系統原始的進程了, js 裏模擬的.前端框架

這是啓動進程的語法: generator 函數做爲參數, 傳給 go 函數執行.babel

import {go} from 'js-csp';

go(function* () {
  console.log('something!');
});

// terminal output:
//
// => something!

例 2: 進程能夠暫停

使用 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!

例 3: 進程等待來自管道的數據

第二個要學的概念是管道, 也是最後一個了. 管道就像是隊列.
一旦進程對管道調用 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

例 4: 進程經過管道來通訊

管道的另外一邊, 往管道里 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

立 5: 管道也是隊列

因爲管道是隊列, 當進程從管道取走數據, 其餘進程就拿不到了.
因此推數據的是一個進程, 取數據的也是一個進程.

下面這個例子能夠看到第二個進程永遠不會打印 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

例 6: 帶緩衝的管道不會在 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!

例 7: Dropping And Sliding Buffers

固定大小的緩衝在 N 個數據以後會阻塞, 初次以外, 還有對緩衝的 dropping 和 sliding 控制.

緩衝的 dropping 覺得着管道能夠持有 N 個數據.
再增長額外的數據放進管道, 管道就會將其丟棄.

緩衝的 sliding 也能夠持有 N 個數據. 不過相對於直接丟棄新數據,
sliding 緩衝原先的第一個推的數據會被丟棄, buffer 裏會留下新的這個數據.

下面這個例子, value Bvalue 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

相關文章
相關標籤/搜索