避免Zalgo
Zalgo: 一種 JavaScript 開發人員虛構的瘋狂惡魔,取名 Zalgo,用來描述 JavaScript 中同步 / 異步的混亂。Zalgo是一個互聯網傳說,會導致世界錯亂,死亡和毀滅的一個不詳實體。
有興趣的同學可以在如下網址上找到Isaac Z. Schlueter的原始帖子
https://blog.izs.me/2013/08/designing-apis-for-asynchrony
不可預測的危險的函數
我們來看一下下面這個危險的函數,它希望實現的目的是如果沒有緩衝是異步讀取文件,如果有緩存則同步讀取緩存。請大家注意,這並不是一個好的實踐。它的行爲是難以預測的。
const fs = require('fs')
const cache = {}
function inconsistentRead(filename, cb) {
if (cache[filename]) {
// 同步調用!!!
cb(cache[filename])
} else {
// 異步調用!!!
fs.readFile(filename, 'utf8', (err,data) => {
cache[filename] = data
cb(data)
})
}
}
解決上述的問題
我們需要明確,API明確定義其特性:同步的或者是異步的
使用同步API
使用fs.readFileSync()函數替代其異步函數,使其變成純同步函數,代碼如下:
const fs = require('fs')
const cache = {}
function consistentRead(filename) {
if (cache[filename]) {
return cache[filename]
} else {
// 改用同步讀取文件
cache[filename] = fs.readFileSync(filename, 'utf8')
return cache[filename]
}
}
當然使用同步API代替異步API有如下注意事項:
-
用於特定功能的同步API可能並不總是可用的
-
同步API將阻塞事件循環,打破了JavaScript併發模型,是整個應用程序變慢。
在Node.js中很多情況下都不建議使用同步I/O, 然而,在某些情況下,這可能是最簡單和最有效的方案。所以我們需要評估不同的應用場景,來選擇不同的方案。
如果靜態文件的數量有限,可以使用同步影響不大,在啓動應用程序時,使用同步阻塞加載配置文件也是比較好的選擇。如果必須一次性讀取許多文件,那就不一樣了。
使用異步,延遲執行
使用process.nextTick()來延遲執行以實現異步執行回調
const fs = require('fs')
const cache = {}
function inconsistentRead(filename, cb) {
if (cache[filename]) {
// 使用process.nextTick() 延遲一個函數異步執行
process.nextTick(() => cb(cache[filename]))
} else {
fs.readFile(filename, 'utf8', (err,data) => {
cache[filename] = data
cb(data)
})
}
}
process.nextTick() 的功能非常簡單,就是將回調函數作爲參數,並將其推到事件隊列的頂部,在任何等待處理的I/O事件之前返回,一旦事件循環再次運行,該回調將被執行。
我們依然需要注意一個問題,如果在遞歸調用process.nextTick() 的時候,會導致I/O飢餓的問題,這個情況下,我們可以採用setImmediate()來替代。他們的作用雖然非常相似,但是語義卻完全不同,process.nextTick() 延遲迴調在任何其他I/O事件觸發之前運行,setImmediate()的回調執行將在隊列中已有的任何I/O事件之後排隊。