《node.js設計模式》學習筆記——同步異步調用問題

避免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有如下注意事項:

  1. 用於特定功能的同步API可能並不總是可用的

  2. 同步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事件之後排隊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章