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'