cake是CoffeeScript自带的make工具,简单易用。不过有一个问题,因为Node.js默认是异步的,所以你很难保证执行顺序。
如何解决呢?其实用回调函数就可以。
例如,假定我们平时使用config.json
(方便开发和测试),但是在发布NPM包的时候,希望发布config.json.example
。这就要求我们在npm publish
前后修改文件名——也就是说,要保证执行的顺序,不能出现文件名还没改好就发布的情况。如果用sh表达的话,就是:
mv config.json config.json.example
npm publish
mv config.json.example config.json
硬来的话,是这么写:
{spawn} = require 'child_process'
spawn 'mv', ['config.json', 'config.json.example']
spawn 'npm', ['publish']
spawn 'mv', ['config.json.example', 'congfig.json']
硬蛋糕可不好吃!这样是不行的,因为child_process.spawn
是异步的。
所以,我们需要把上面的代码改成回调的形式,确保上一步退出时才调用下一步:
mv = spawn 'mv', ['config.json', 'config.json.example']
mv.on 'exit', ->
npm_publish = spawn 'npm', ['publish']
npm_publish.on 'exit', ->'
spawn 'mv', ['config.json.example', 'congfig.json']
这样就可以了。可是每次这么写还蛮烦的。我们把它抽象成一个函数。
嗯,该怎么写呢?首先,我们确定这个函数的输入,函数应当接受一个命令,后面是一串回调函数。因此:
shell_commands = (args...) ->
args...
是splats,用来表示参数数目不定。
然后我们需要做的就是执行这个命令,在命令执行完毕之后,运行那一串回调函数。
因此,我们首先得到需要执行的命令,这很简单,pop掉最后的一串回调函数,再交给spawn执行即可。
args.pop()
[command, args...] = args
spawn command, args
注意上面的args...
同样是splats,这次用于模式匹配。
然后加上运行回调函数的代码,我们的函数基本就成形了:
shell_commands = (args...) ->
callback = args.pop()
[command, args..] = args
shell_command = spawn command, args
shell_command.on 'exit' ->
callback()
有个问题,回调到最后,会是单纯的命令,没有回调函数了。因此我们加一个判断,如果args
的最后一项不是函数的话,那就不pop了,直接执行命令就行了:
shell_commands = (args...) ->
if typeof(args[a.length - 1]) is 'function'
callback = args.pop()
else
callback = ->
[command, args..] = args
shell_command = spawn command, args
shell_command.on 'exit' ->
callback()
抽象出了这个函数以后,我们原先的代码就可以这么写了:
shell_commands 'mv', 'config.json', 'config.json.example', ->
shell_commands 'npm', 'publish', ->
shell_commands 'mv', 'config.json.example', 'config.json'
是不是清爽多了?
没有处理的问题是,万一在重命名的过程中出问题了,那么我们不应该发布NPM包。所以,我们加一下判断,如果进程的exit code不为0(这就意味着有错误),那么继续执行。
shell_commands = (args...) ->
if typeof(args[a.length - 1]) is 'function'
callback = args.pop()
else
callback = ->
[command, args..] = args
shell_command = spawn command, args
shell_command.on 'exit', (code) ->
if code isnt 0
callback(code)
else
console.log("Exited with status: " + code)
相应地,调用的时候也需要传入code
:
shell_commands 'mv', 'config.json', 'config.json.example', (code) ->
shell_commands 'npm', 'publish', (code) ->
shell_commands 'mv', 'config.json.example', 'config.json'
通常大家喜欢verbose的输出,那么我们就在终端显示一下执行的命令:
console.log(command, " ", args.join(" "))
最终的CakeFile类似这样:
{spawn} = require 'child_process'
shell_commands = (args...) ->
if typeof(args[a.length - 1]) is 'function'
callback = args.pop()
else
callback = ->
[command, args..] = args
console.log(command, " ", args.join(" "))
shell_command = spawn command, args
shell_command.on 'exit', (code) ->
if code isnt 0
callback(code)
else
console.log("Exited with status: " + code)
task "publish", "publish NPM", ->
shell_commands 'mv', 'config.json', 'config.json.example', (code) ->
shell_commands 'npm', 'publish', (code) ->
shell_commands 'mv', 'config.json.example', 'config.json'
task "test", "run unit tests", ->
shell_commands './node_module/.bin/mocha'