[译] 快速介绍JavaScript中的CSP

Communicating Sequential Processes 的 7 个示例

CSP 是什么? 一般来说, 它是写并行代码的一套方案.

在 Go 语言里自带该功能, Clojure 通过基于 Macro 的 core.async 来实现,

现在 JavaScript 通过 Generator 也能做支持了, 或者说 ES6 的功能.

为什么我要关心 CSP? 因为它强大啊, 而且高效, 而且简单. 都这样了你还想要什么? :)

好吧, 说细节. 怎样使用呢?我们用 js-csp, 而且需要 generator 支持, ES6 才有.

也就说 Node 4 或者更高的版本才行, 或者浏览器代码用 Babel 编译一下,

当然能其他的编译工具可能也行, 但你要确认下是支持 Generator 的.

注: 文章写得早, 现在翻译文章, Chrome 应该是支持 Generator 的.

扯多了, 来看例子吧!

例 1: 进程

第一个要学的概念是"进程". 进程可以执行代码, 简单说就是这样的了. :)

注: 当然不是操作系统原始的进程了, js 里模拟的.

这是启动进程的语法: generator 函数作为参数, 传给 go 函数执行.


  1. import {go} from 'js-csp'; 
  2.  
  3. go(function* () { 
  4.   console.log('something!'); 
  5. }); 
  6.  
  7. // terminal output: 
  8. // 
  9. // => something!  

例 2: 进程可以暂停

使用 yield 关键字可以暂停一个进程, 把当前进程的占用释放:


  1. import {go, timeout} from 'js-csp'; 
  2.  
  3. go(function* () { 
  4.   yield timeout(1000); 
  5.   console.log('something else after 1 second!'); 
  6. }); 
  7.  
  8. console.log('something!'); 
  9.  
  10. // terminal output: 
  11. // 
  12. // => something! 
  13. // => something else after 1 second!  

例 3: 进程等待来自管道的数据

第二个要学的概念是管道, 也是最后一个了. 管道就像是队列.

一旦进程对管道调用 take, 进程就会暂停, 直到别人往管道放进数据.


  1. import {go, chan, take, putAsync} from 'js-csp'; 
  2.  
  3. let ch = chan(); 
  4.  
  5. go(function* () { 
  6.   const received = yield take(ch); 
  7.   console.log('RECEIVED:', received); 
  8. }); 
  9.  
  10. const text = 'something'; 
  11. console.log('SENDING:', text); 
  12.  
  13. // use putAsync to put a value in a 
  14. // channel from outside a process 
  15. putAsync(ch, text); 
  16.  
  17. // terminal output: 
  18. // 
  19. // => SENDING: something 
  20. // => RECEIVED: something  

例 4: 进程通过管道来通信

管道的另一边, 往管道里 put 数据的那些进程也会暂停, 直到这边进程调用 take.

下面的例子就复杂一点了, 试着跟随一下主线, 印证一下终端输出的内容:


  1. import {go, chan, take, put} from 'js-csp'; 
  2.  
  3. let chA = chan(); 
  4. let chB = chan(); 
  5.  
  6. // Process A 
  7. go(function* () { 
  8.   const receivedFirst = yield take(chA); 
  9.   console.log('A > RECEIVED:', receivedFirst); 
  10.  
  11.   const sending = 'cat'; 
  12.   console.log('A > SENDING:', sending); 
  13.   yield put(chB, sending); 
  14.  
  15.   const receivedSecond = yield take(chA); 
  16.   console.log('A > RECEIVED:', receivedSecond); 
  17. }); 
  18.  
  19. // Process B 
  20. go(function* () { 
  21.   const sendingFirst = 'dog'; 
  22.   console.log('B > SENDING:', sendingFirst); 
  23.   yield put(chA, sendingFirst); 
  24.  
  25.   const received = yield take(chB); 
  26.   console.log('B > RECEIVED:', received); 
  27.  
  28.   const sendingSecond = 'another dog'; 
  29.   console.log('B > SENDING:', sendingSecond); 
  30.   yield put(chA, sendingSecond); 
  31. }); 
  32.  
  33. // terminal output: 
  34. // 
  35. // => B > SENDING: dog 
  36. // => A > RECEIVED: dog 
  37. // => A > SENDING: cat 
  38. // => B > RECEIVED: cat 
  39. // => B > SENDING: another dog 
  40. // => A > RECEIVED: another dog  

例5: 管道也是队列

由于管道是队列, 当进程从管道取走数据, 其他进程就拿不到了.所以推数据的是一个进程, 取数据的也是一个进程.

下面这个例子可以看到第二个进程永远不会打印 B > RECEIVED: dog,

因为第一个进程已经把数据取走了.


  1. import {go, chan, take, put} from 'js-csp'; 
  2.  
  3. let ch = chan(); 
  4.  
  5. go(function* () { 
  6.   const text = yield take(ch); 
  7.   console.log('A > RECEIVED:', text); 
  8. }); 
  9.  
  10. go(function* () { 
  11.   const text = yield take(ch); 
  12.   console.log('B > RECEIVED:', text); 
  13. }); 
  14.  
  15. go(function* () { 
  16.   const text = 'dog' 
  17.   console.log('C > SENDING:', text); 
  18.   yield put(ch, text); 
  19. }); 
  20.  
  21. // terminal output: 
  22. // 
  23. // => C > SENDING: dog 
  24. // => A > RECEIVED: dog  

例 6: 带缓冲的管道不会在 put 操作时阻塞

管道可以带缓冲, 也就是, 一定数量之内的数据, 执行 put 操作可以避开阻塞.

这个例子里, 即便没有其他进程调用 take, 前两个写操作也不会阻塞进程.

不过管道的缓存数量是 2, 所以第三个数据就阻塞进程了, 直到其他进程取走数据.


  1. import {go, chan, put, buffers} from 'js-csp'; 
  2.  
  3. let ch = chan(buffers.fixed(2)); 
  4.  
  5. go(function* () { 
  6.   yield put(ch, 'value A'); 
  7.   yield put(ch, 'value B'); 
  8.   console.log('I should print!'); 
  9.   yield put(ch, 'value C'); 
  10.   console.log('I should not print!'); 
  11. }); 
  12.  
  13. // terminal output: 
  14. // 
  15. // => I should print!  

例 7: Dropping And Sliding Buffers

固定大小的缓冲在 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 的缓冲永远不会阻塞!


  1. let droppingCh = chan(buffers.dropping(1)); 
  2. let slidingCh  = chan(buffers.sliding(1)); 
  3.  
  4. go(function* () { 
  5.   yield put(droppingCh, 'value A'); 
  6.   yield put(droppingCh, 'value B'); 
  7.   yield put(droppingCh, 'value C'); 
  8.   console.log('DROPPING:', yield take(droppingCh)); 
  9. }); 
  10.  
  11. go(function* () { 
  12.   yield put(slidingCh, 'value A'); 
  13.   yield put(slidingCh, 'value B'); 
  14.   yield put(slidingCh, 'value C'); 
  15.   console.log('SLIDING:', yield take(slidingCh)); 
  16. }); 
  17.  
  18. // terminal output: 
  19. // 
  20. // => DROPPING: value A 
  21. // => SLIDING: value C  

结论

CSP 用了一段时间之后, 用回调或者 Promise 写代码就像是侏罗纪的技术.

我希望 ES6 的 Generator 能帮助 CSP 成为 JavaScript 的一个标准,

就像是 Go 已经是的那样, 以及 Clojure 里正在成为的那样.

下一步

另外有两个模型也还有意思, 大概可以认为是比 CSP 层级更高一点的:

函数式也是响应式编程(Rx)跟 Actors, 分别在 Rx 和 Erlang 里用到.

我当然后面也会写博客来挖掘一下.

我同时相信 CSP 对于前端框架来说非常棒.

作者:题叶

来源:51CTO

时间: 2024-11-02 08:26:48

[译] 快速介绍JavaScript中的CSP的相关文章

使用sourcemap快速定位javascript中的问题

大家都有过用-min.js开发的经历,但这样的脚本调试非常头疼.如果使用为压缩版的,上线前又要去压缩,sourcemap的出现完美解决了这一问题. 即便是chrome提供了格式化代码但阅读压缩后的代码环视很吃力的. 以angularjs为例,谈谈sourcemap的使用. 我的文件夹结构如下: 由于google经常被墙所以建议把angular.js angular.min.js angular.min.js.map都下载到本地. 并且修改angular.min.js中sourcemap的配置,指

介绍JavaScript中Math.abs()方法的使用

 这个方法返回一个数字的绝对值. 语法   1 Math.abs( x ) ; 下面是参数的详细信息: x : 一个数字 返回值: 返回一个数字的绝对值 例子:   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <html> <head> <title>JavaScript Math abs() Method</title> </head> <body> <scrip

简单介绍JavaScript中字符串创建的基本方法_基础知识

创建一个字符串有几种方法.最简单的是用引号将一组字符包含起来,可以将其赋值给一个字符串变量. var myStr = "Hello, String!";       可以用双引号或单引号将字符串包含,但要注意,作为界定字符串的一对引号必须是相同的,不能混用.       像var myString = "Fluffy is a pretty cat.'; 这样的声明就是非法的.       允许使用两种引号,使得某些操作变得简单,比如将一种嵌入另外一种: document.w

介绍JavaScript中Math.abs()方法的使用_基础知识

 这个方法返回一个数字的绝对值. 语法 Math.abs( x ) ; 下面是参数的详细信息:     x : 一个数字 返回值: 返回一个数字的绝对值 例子: <html> <head> <title>JavaScript Math abs() Method</title> </head> <body> <script type="text/javascript"> var value = Math.

Javascript中如何引用指针

本文介绍Javascript中引用指针的方法. 请尝试完成下列完形填空: /* 创建一个队列,头为head0,尾为tail0 */ function IntList(head0, tail0){ this.head = head0 || 0; this.tail = tail0 || null; } /* 返回一个IntList包含数组中的所有数 */ IntList.list = function(__args){ var sentinel = new IntList(), len = __a

Javascript中如何将字符串转为数字

本文介绍Javascript中数字转字符串及字符串转数字的方法 Javascript中最简洁的数字转字符串方法是: var num = 123; var string = num + ""; 也就是在数字后面加上一个空字符.那么最简洁字符串转数字方法呢? 字符串只能进行加法(拼接) 字符串进行加法(拼接)是很常见的,但是字符串进行减法.乘法.除法呢? 这似乎很难定义,实际上字符串没有减法.乘法.除法操作. 但Javascript是动态语言,如果你拿两个字符串进行这三种操作的时候,他会尝

JavaScript中错误正确处理方式,你用对了吗?

JavaScript的事件驱动范式增添了丰富的语言,也是让使用JavaScript编程变得更加多样化.如果将浏览器设想为JavaScript的事件驱动工具,那么当错误发生时,某个事件就会被抛出.理论上可以认为这些发生的错误只是JavaScript中的简单事件. 本文将会讨论客户端JavaScript中的错误处理.主要介绍JavaScript中的易犯错误.错误处理.异步代码编写等内容. 下面就让我们一起看看如何正确处理JavaScript中的错误. Demo演示 本文中使用的demo可以在GitH

JavaScript中this的四个绑定规则总结_javascript技巧

前言 如果要问javascript中哪两个知识点容易混淆,作用域查询和this机制绝对名列前茅.所以这篇文章开始将介绍javascript中this的四个绑定规则,下面来一起看看吧. 绑定规则 1. 默认绑定 独立函数调用时,this 指向全局对象,如果使用严格模式,那么全局对象无法使用默认绑定, this绑定至 undefined. function foo() { console.log(this.a); } var a = 2; foo(); // 2 严格模式时: function fo

全面了解javascript中的错误处理机制_javascript技巧

前面的话 错误处理对于web应用程序开发至关重要,不能提前预测到可能发生的错误,不能提前采取恢复策略,可能导致较差的用户体验.由于任何javascript错误都可能导致网页无法使用,因此作为开发人员,必须要知道何时可能出错,为什么会出错,以及会出什么错.本文将详细介绍javascript中的错误处理机制 error对象 error对象是包含错误信息的对象,是javascript的原生对象.当代码解析或运行时发生错误,javascript引擎就会自动产生并抛出一个error对象的实例,然后整个程序