资讯专栏INFORMATION COLUMN

js-csp 异步编程的一些简单的例子

curried / 2668人阅读

摘要:发现一个月没刷技术文章了有点慌整理一篇短的用法出来只包含最基本的用法在里边最清晰不过我是在写的版本的实现包含异步用法会更繁琐一些但是也值得看看我相信普及之前还是一个很有意思的选择我的代码写的是可以自动脑补圆括号花括号上去注意包含的函数自动

发现一个月没刷技术文章了, 有点慌, 整理一篇短的 CSP 用法出来,
只包含最基本的用法, 在 Go 里边最清晰, 不过我是在 Clojure 写的 CSP.
js 版本的 CSP 实现包含异步, 用法会更繁琐一些, 但是也值得看看.
我相信 async/await 普及之前, js-csp 还是一个很有意思的选择.

我的代码写的是 CoffeeScript, 可以自动脑补圆括号花括号上去...
注意包含 yield 的函数自动被转成 function*() {}, 所以注意脑补.
脑补不出来的只好贴在这边编译下了 http://coffeescript.org/

使用 timeout

首先是最基本的 CSP 的例子, 也就是用同步的代码写异步的逻辑,
CSP 当中最核心的概念是 Channel, 最简单的 csp.timeout(1000) 创建 channel.

</>复制代码

  1. csp = require "js-csp"
  2. # 用 csp.go 启动一个 yield 函数
  3. csp.go ->
  4. # 有 csp.take 从这个管道取出数据, yield 来模拟阻塞的效果
  5. yield csp.take csp.timeout(1000)
  6. console.log "Gone 1s"

运行一下:

</>复制代码

  1. =>> coffee async.coffee
  2. Gone 1s

我注意到对于 timeout 来说, 省掉 csp.take 也是能够正常运行的:

</>复制代码

  1. csp = require "js-csp"
  2. csp.go -> # 脑补 yield 函数
  3. yield csp.timeout 1000
  4. console.log "Gone 1s"
  5. yield csp.timeout 2000
  6. console.log "Gone 2s"
  7. yield csp.timeout 3000
  8. console.log "Gone 3s. End"

运行一下:

</>复制代码

  1. =>> coffee async.coffee
  2. Gone 1s
  3. Gone 2s
  4. Gone 3s. End
使用 put 和 take

csp.timeout 比较特殊, 默认就会产生数据, 只要进行 csp.take 就好了.
一般的 Channel 的话, 需要手动创建出来, 然后手动推数据,
比如下面的代码创建了一个数据, 用 csp.go 启动另一个"进程"往 Channel 推数据,
这里的"进程"的说法并不是真正的进程, 只是模拟进程的行为:

</>复制代码

  1. csp = require "js-csp"
  2. talk = (ch) ->
  3. yield csp.timeout 3000
  4. console.log "Done 3s timeout"
  5. # 等待 3s 然后往 Channel 当中写入数据, yield 会产生等待
  6. yield csp.put ch, "some result"
  7. csp.go ->
  8. ch = csp.chan()
  9. # 启动另一个"进程"
  10. csp.go talk, [ch] # 数组里是传给 talk 函数的参数
  11. # 使用 yield.take 从 Channel 取出数据, 使用 yield 模拟等待
  12. result = yield csp.take ch
  13. console.log "Result:", JSON.stringify(result)

运行一下:

</>复制代码

  1. =>> coffee async.coffee
  2. Done 3s timeout
  3. Result: "some result"
假装有两个进程

同样是上边的代码, 只是调整一下写法, 看上去像是分别启动了两个"进程",
虽然它们的运行时独立的, 但是可以通过管道进行通信,
而且在对应的 csp.takecsp.put 操作过程中, 会通过 yield 进行等待:

</>复制代码

  1. csp = require "js-csp"
  2. talk = (ch) ->
  3. yield csp.timeout 3000
  4. console.log "Done 3s timeout"
  5. yield csp.put ch, "some result"
  6. listen = (ch) ->
  7. result = yield csp.take ch
  8. console.log "Result:", JSON.stringify(result)
  9. # 创建 Channel, 启动两个"进程"
  10. theCh = csp.chan()
  11. # csp.go 后面第一个是 yield 函数, 第二个是参数的数组, 虽然比较难看
  12. csp.go talk, [theCh]
  13. csp.go listen, [theCh]

运行一下:

</>复制代码

  1. =>> coffee async.coffee
  2. Done 3s timeout
  3. Result: "some result"
封装异步事件

实际使用当中, 会需要把 js 环境的异步代码封装成管道的形式,
不封装成管道, 就不能借助 csp.go 来封装同步代码了,
由于 js 不像 Go 那样整个语言层面做了处理, 实际上会有奇怪的写法,
所以 js-csp 提供了 csp.putAsynccsp.takeAsync:

</>复制代码

  1. csp = require "js-csp"
  2. talk = (ch) ->
  3. setTimeout ->
  4. csp.putAsync ch, "some result"
  5. console.log "Finished 3s of async"
  6. , 3000
  7. listen = (ch) ->
  8. result = yield csp.take ch
  9. console.log "Result:", JSON.stringify(result)
  10. theCh = csp.chan()
  11. talk theCh
  12. csp.go listen, [theCh]

运行一下:

</>复制代码

  1. =>> coffee async.coffee
  2. Finished 3s of async
  3. Result: "some result"
处理超时

一个操作是否超时的问题, 可以同时启动一个定时的"进程",
然后观察两个"进程"哪一个先执行完成, 从而判断是否超时,
这就用到了 csp.alts 函数, 这个奇怪的命名是用 Clojure 带过来的:

</>复制代码

  1. csp = require "js-csp"
  2. talk = (ch) ->
  3. time = Math.random() * 4 * 1000
  4. setTimeout ->
  5. console.log "Get result after #{time}ms"
  6. csp.putAsync ch, "some result"
  7. , time
  8. listen = (ch) ->
  9. hurry = csp.timeout 2000
  10. # 通过 csp.alts 同时等待多个 Channel 返回数据
  11. result = yield csp.alts [ch, hurry]
  12. # result.channel 可以用于判断数据的来源, result.value 才是真正的数据
  13. if result.channel is hurry
  14. console.log "Too slow, got no result"
  15. # close 只是设置 Channel 的状态, 其实还需要手工处理一些逻辑
  16. hurry.close()
  17. else
  18. console.log "Fast enough, got", JSON.stringify(result.value)
  19. theCh = csp.chan()
  20. talk theCh
  21. csp.go listen, [theCh]

用了随机数, 运行多次试一下, 可以看到根据不同的时间, 结果是不一样的:

</>复制代码

  1. =>> coffee async.coffee
  2. Too slow, got no result
  3. Get result after 3503.6168682995008ms
  4. =>> coffee async.coffee
  5. Too slow, got no result
  6. Get result after 3095.264637685924ms
  7. =>> coffee async.coffee
  8. Get result after 703.6501633183257ms
  9. Fast enough, got "some result"
  10. =>> coffee async.coffee
  11. Too slow, got no result
  12. Get result after 3729.5125755664317ms
  13. =>> coffee async.coffee
  14. Get result after 101.51519531067788ms
  15. Fast enough, got "some result"
循环任务

yield 用法类似, 如果有循环的代码, 也可以用 CSP 写出来,
这个的话不用怎么想应该能明白了, loop 只是 while true 的语法糖:

</>复制代码

  1. csp = require "js-csp"
  2. chatter = (ch) ->
  3. counter = 0
  4. loop
  5. yield csp.timeout 1000
  6. counter += 1
  7. yield csp.put ch, counter
  8. repeat = (ch) ->
  9. loop
  10. something = yield csp.take ch
  11. console.log "Hear something:", something
  12. theCh = csp.chan()
  13. csp.go chatter, [theCh]
  14. csp.go repeat, [theCh]

运行一下:

</>复制代码

  1. =>> coffee async.coffee
  2. Hear something: 1
  3. Hear something: 2
  4. Hear something: 3
  5. Hear something: 4
  6. ^C
多个数据的消费者

实际场景当中会遇到多个消费者从单个生产者读取数据的需求,
这是一个用 Channel 比较合适的场景, 启动两个"进程"读取一个 Channel 就好了,
下面我模拟的是不同的处理时间 300ms 和 800ms 读取 100ms 频率的数据,
因为 CSP 自动处理了等待, 整个代码看上去挺简单的:

</>复制代码

  1. csp = require "js-csp"
  2. chatter = (ch) ->
  3. counter = 0
  4. loop
  5. yield csp.timeout 100
  6. counter += 1
  7. yield csp.put ch, counter
  8. repeat = (ch) ->
  9. loop
  10. yield csp.timeout 800
  11. something = yield csp.take ch
  12. console.log "Hear at 1:", something
  13. repeat2 = (ch) ->
  14. loop
  15. yield csp.timeout 300
  16. something = yield csp.take ch
  17. console.log "Hear at 2:", something
  18. theCh = csp.chan()
  19. csp.go chatter, [theCh]
  20. csp.go repeat, [theCh]
  21. csp.go repeat2, [theCh]

运行一下:

</>复制代码

  1. =>> coffee async.coffee
  2. Hear at 2: 1
  3. Hear at 2: 2
  4. Hear at 1: 3
  5. Hear at 2: 4
  6. Hear at 2: 5
  7. Hear at 2: 6
  8. Hear at 1: 7
  9. Hear at 2: 8
  10. Hear at 2: 9
  11. Hear at 1: 10
  12. Hear at 2: 11
  13. Hear at 2: 12
  14. Hear at 2: 13
  15. Hear at 1: 14
  16. Hear at 2: 15
  17. Hear at 2: 16
  18. Hear at 1: 17
  19. Hear at 2: 18
  20. Hear at 2: 19
  21. Hear at 2: 20
  22. Hear at 1: 21
  23. Hear at 2: 22
  24. Hear at 2: 23
  25. Hear at 1: 24
  26. ^C
使用 buffer

默认情况下管道是阻塞的, csp.put csp.take 成对进行,
也就是说, 只有一个就绪的话, 它会等待另一个开始, 然后一起执行,
但是用 buffer 的话, 管道就会先在一定范围内进行缓存,
这样 csp.put 就可以先运行下去了, 这个是不难理解的...
管道实际上有 3 种策略, fixed, dropping, sliding:

fixed, 缓存放满以后就会开始形成阻塞了

dropping, 缓存满了以后, 新的数据就会丢弃

sliding, 缓存满以后, 会丢弃掉旧的数据让新数据能放进缓存

随便演示一个丢弃数据的例子:

</>复制代码

  1. csp = require "js-csp"
  2. chatter = (ch) ->
  3. counter = 0
  4. loop
  5. yield csp.timeout 200
  6. counter += 1
  7. console.log "Write data:", counter
  8. yield csp.put ch, counter
  9. repeat = (ch) ->
  10. loop
  11. yield csp.timeout 300
  12. something = yield csp.take ch
  13. console.log "Hear:", something
  14. theCh = csp.chan(csp.buffers.dropping(3))
  15. csp.go chatter, [theCh]
  16. csp.go repeat, [theCh]

运行一下, 可以看到 "Hear" 部分丢失了一些数据, 但前三个数据不会丢:

</>复制代码

  1. =>> coffee async.coffee
  2. Write data: 1
  3. Hear: 1
  4. Write data: 2
  5. Hear: 2
  6. Write data: 3
  7. Write data: 4
  8. Hear: 3
  9. Write data: 5
  10. Hear: 4
  11. Write data: 6
  12. Write data: 7
  13. Hear: 5
  14. Write data: 8
  15. Hear: 6
  16. Write data: 9
  17. Write data: 10
  18. Hear: 7
  19. Write data: 11
  20. Hear: 8
  21. Write data: 12
  22. Write data: 13
  23. Hear: 9
  24. Write data: 14
  25. Hear: 11
  26. Write data: 15
  27. Write data: 16
  28. Hear: 12
  29. Write data: 17
  30. Hear: 14
  31. ^C
小结

由于 CSP 是在 Go 语言发明的, 完整的用法还是看 Go 的教程比较好,
到了 Clojure 和 js 当中难免会增加一些坑, 特别是 js 当中...
上面提到的 API 在 js-csp 的文档上有描述, 例子也有, 但是挺少的:

https://github.com/ubolonton/...

https://github.com/ubolonton/...

另外还有一些高级一点的用法, 比如数据的 transform 和 pipe 之类的,
其实就是 Stream 的用法在 Channel 上的改版, 某种程度上 Channel 也是 Stream,
对于我个人来说, Channel 的抽象比起 Stream 的抽象舒服多了.

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86652.html

相关文章

  • js-csp 可以开始尝试了

    摘要:的用例的用法最早是语言传开来的看一下我从网上扒的代码其中符号是往当中写入数据的操作同时注意一般的位置对于来说是阻塞的由于能够处理异步操作也就是说能做到异步代码用同步写法更多的细节搜索应该就能找到除了也实现了对于的支持也就是 CSP 的用例 CSP 的用法最早是 Go 语言传开来的, 看一下我从网上扒的代码: package main import fmt func ping(pin...

    tracymac7 评论0 收藏0
  • [译] 快速介绍 JavaScript 中 CSP

    摘要:原文的个示例是什么一般来说它是写并行代码的一套方案在语言里自带该功能通过基于的来实现现在通过也能做支持了或者说的功能为什么我要关心因为它强大啊而且高效而且简单都这样了你还想要什么好吧说细节怎样使用呢我们用而且需要支持才有也就说或者更高的版 原文 http://lucasmreis.github.io/b... Communicating Sequential Processes 的 7...

    Rocko 评论0 收藏0
  • React 组件之间如何交流

    摘要:前言今天群里面有很多都在问关于组件之间是如何通信的问题,之前自己写的时候也遇到过这类问题。英文能力有限,如果有不对的地方请跟我留言,一定修改原著序处理组件之间的交流方式,主要取决于组件之间的关系,然而这些关系的约定人就是你。 前言 今天群里面有很多都在问关于 React 组件之间是如何通信的问题,之前自己写的时候也遇到过这类问题。下面是我看到的一篇不错英文版的翻译,看过我博客的人都知道...

    tomlingtm 评论0 收藏0
  • ES6 Generator实现协同程序

    摘要:关键字表示代码在该处将会被阻塞式暂停阻塞的仅仅是函数代码本身,而不是整个程序,但是这并没有引起函数内部自顶向下代码的丝毫改变。通过实现模式在通过实现理论的过程中已经有一些有趣的探索了。 至此本系列的四篇文章翻译完结,查看完整系列请移步blogs 由于个人能力知识有限,翻译过程中难免有纰漏和错误,望不吝指正issue ES6 Generators: 完整系列 The Basics...

    MudOnTire 评论0 收藏0
  • js 异步编程

    摘要:总结这篇文章简单的介绍了一些常用的异步编程的方法,如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞收藏。 大家都知道js的执行环境是单线程的,如果没有异步编程,那么js的执行效率会非常低下,导致程序十分卡顿,一提到异步编程大家首先的想到的一定是回调函数,这也是最常用的异步编程的形式,但其实常用的还有Promise和Async函数,接下来就让我们一起学习这几种常用的异步编程方法...

    diabloneo 评论0 收藏0

发表评论

0条评论

curried

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<