通過不同的方式讀取在 files 文件夾下的三個文件來引出 promise 在處理異步時與回調函數相比的優勢,files 文件夾有三個文件 a.json,b.json,c.json。
// a.json
{
"content": "this is a.json",
"next": "b.json"
}
// b.json
{
"content": "this is b.json",
"next": "c.json"
}
// c.json
{
"content": "this is c.json",
"next": null
}
現在要依次讀取這三個文件,並且 b.json 的文件名要通過 a.json 文件中的 next 屬性獲得,c.json 的文件名要通過 b.json 的文件名獲得。
首先我們先來看一下讀取文件時輸出內容的格式
const fs = require('fs')
const path = require('path')
// 回調函數且不封裝為函數的方式
const fullFileName = path.resolve(__dirname, 'files', 'a.json')
fs.readFile(fullFileName, (err, data) => {
console.log(data)
})
從文件中直接讀取出來的是二進制的形式
data 是二進制形式
data 轉換為字符串
console.log(data.toString());
data 轉換為對象
console.log(JSON.parse(data.toString());
現在我們用最符合人思維方式的寫法來一次讀取三個文件的內容。
// 回調函數且不封裝為函數的方式
const fullFileName = path.resolve(__dirname, 'files', 'a.json')
// 讀取a.json
fs.readFile(fullFileName, (err, data) => {
console.log(JSON.parse(data.toString()))
const fileName = JSON.parse(data.toString()).next
const fullFileName = path.resolve(__dirname, 'files', fileName)
// 從a.json中獲得b.json文件名,然后讀取b.json
fs.readFile(fullFileName, (err, data) => {
console.log(JSON.parse(data.toString()))
const fileName = JSON.parse(data.toString()).next
const fullFileName = path.resolve(__dirname, 'files', fileName)
// 從b.json中獲取c.json文件名,然后讀取c.json
fs.readFile(fullFileName, (err, data) => {
console.log(JSON.parse(data.toString()))
})
})
})
讀取結果:
上面這樣寫代碼復用性很低,我們可以考慮將讀取文件內容封裝為一個函數,這樣每次讀取文件內容時直接調用那個函數就可以了。
// 將讀取文件內容封裝成一個函數
function readFileContent(fileName) {
fs.readFile(fileName, (err, data) => {
console.log(JSON.parse(data.toString()))
})
}
// 讀取a.json的內容
const fullFileName = path.resolve(__dirname, 'files', 'a.json')
readFileContent(fullFileName)
讀取結果:
如果我們想要完成連續讀取三個文件,並且下一個文件的文件名來自上一個文件,上面封裝的函數顯然是不能滿足要求的。
上面回調函數的內容是 console.log(JSON.parse(data.toString()))
,這樣寫死的顯然不能再讀取下一個文件,如果我們將 readFileContent
的第二個參數變成一個函數,然后在回調函數中調用執行,那么在這個函數中我們就可以再次讀取下一個文件。
// 封裝連續讀取文件的函數
function readFileContent(fileName, callback) {
const fullFileName = path.resolve(__dirname, 'files', fileName)
fs.readFile(fullFileName, (err, data) => {
// 這里使用callback時需要傳遞一個參數,那么定義的callback函數也有一個參數
callback(JSON.parse(data.toString()))
})
}
const fileName = 'a.json'
readFileContent(fileName, aData => {
console.log(aData);
// 獲取b.json的名稱
const fileName = aData.next;
// 讀取b.json
readFileContent(fileName, bData => {
console.log(bData)
// 獲取c.json的名稱
const fileName = bData.next
// 讀取c.json
readFileContent(fileName, cData => {
console.log(cData)
})
})
})
像上面這樣寫如果需要讀取的文件繼續增多,那么回調函數就會一直增加下去,呈現金字塔的形狀,函數中間嵌套着函數,導致代碼可讀性較低,這也就是經常說的回調地獄。
關於回調地獄推薦這篇博文,講的很清楚,回調地獄。
解決回調地獄一種比較常用的方法就是使用 promise,關於 promise 的知識在這里就不多說了,現在利用 promise 讀取一個文件的內容。
const promise = new Promise((resolve, reject) => {
const fullFileName = path.resolve(__dirname, 'files', 'a.json');
fs.readFile(fullFileName, (err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
promise.then((data) => {
console.log(JSON.parse(data.toString()))
}, (err) => {
console.log(err)
})
讀取結果
這樣寫很顯然不能完成多個文件的讀取,我們現在也考慮將其封裝為一個函數,如果讓這個函數返回一個 promise 那么調用一次就返回一個 promise,這樣就可以多次讀取文件了。
// 封裝函數利用promise讀取三個文件的內容
function readFileContent(fileName) {
const fullFileName = path.resolve(__dirname, 'files', fileName)
return new Promise((resolve, reject) => {
fs.readFile(fullFileName, (err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
}
const fileName = 'a.json'
readFileContent(fileName).then((data) => {
console.log(JSON.parse(data.toString()));
const fileName = JSON.parse(data.toString()).next;
return readFileContent(fileName)
}).then((data) => {
console.log(JSON.parse(data.toString()));
const fileName = JSON.parse(data.toString()).next;
return readFileContent(fileName)
}).then((data) => {
console.log(JSON.parse(data.toString()));
})
讀取結果:
重點在於第19行和23行的代碼,當在 then 中返回一個新的 promise 時,下一個 then 中的 data 就是這個新的 promise 中 resolve(data) 的參數 data,then 響應的是這個新的 promise。
可以看到當使用 promise 時,不會再出現函數嵌套的情況了,每個 then 都是一個異步操作,條理也比較清晰,因此 promise 也作為一種解決回調地獄比較常見的方式,解決回調地獄更多的方法可以參考上面推薦的那篇博客。
完,如有不恰當之處,還望告知,感謝。