1.回調(diào)的嵌套陷阱
在Node.js中,使用回調(diào)的方式進(jìn)行異步操作,我們以讀取文件內(nèi)容為例:
const fs = require('fs');
// 定義一個以回調(diào)的方式獲取文件的函數(shù)
function asyncReadFile(path, callback) {
fs.readFile(path, 'utf-8', function(err, data) {
callback(err,data)
})
}
// 調(diào)用:
router.get('/', async function(req, res, next) {
asyncReadFile("./package.json", (err, data) {
console.log(data)
// 渲染頁面
res.render('index', { title: 'Express' });
})
})
從上面的調(diào)用不難推測出,以回調(diào)的方式來實(shí)現(xiàn)異步,嵌套將會是開發(fā)者的噩夢:
router.get('/', async function(req, res, next) {
asyncReadFile("./package.json", (err, data1) {
console.log(data1)
asyncReadFile("./app.js", (err, data2) {
console.log(data2)
asyncReadFile("./other.js", (err, data3) {
console.log(data3)
// ...
// 渲染頁面
res.render('index', { title: 'Express' });
})
})
})
})
在業(yè)務(wù)查詢比較多、需要同時觸發(fā)的任務(wù)中,回調(diào)會嚴(yán)重影響排版及閱讀,尤其對接手人(甚至開發(fā)者自己)的閱讀和理解造成了很大的困難。
2.使用async/await
接下來,我將改造上面的 asyncReadFile() 函數(shù),以將返回值其構(gòu)造為 Promise 對象。
var fs = require('fs')
var asyncReadFile = function(path) {
return new Promise(function(resolve, reject) {
fs.readFile(path, 'utf-8', function(err, data) {
resolve(data)
})
})
}
router.get('/', async function(req, res, next) {
var file = await asyncReadFile("./package.json")
console.log(file)
// 渲染
res.render('index', { title: 'Express' });
});
這里需要注意的是,async 和 await 是成對出現(xiàn)的。即你要在哪個函數(shù)使用 await 的方式,就應(yīng)當(dāng)對這個函數(shù)進(jìn)行 async 聲明。
事實(shí)上,這個聲明是一種語法糖,即在不改變語法的基礎(chǔ)上,讓代碼的可讀性更好、更不容易出錯。async就是Generator函數(shù)的語法糖。
Generator函數(shù)的用法是這樣的:
var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
};
對應(yīng)的async用法則是:
var gen = async function (){
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
};
即將 * 和 yield 分別用 async 和 await 替換了。
語法糖(Syntactic sugar),也譯為糖衣語法,是由英國計算機(jī)科學(xué)家彼得·約翰·蘭達(dá)(Peter J. Landin)發(fā)明的一個術(shù)語,指計算機(jī)語言中添加的某種語法,這種語法對語言的功能并沒有影響,但是更方便程序員使用。通常來說使用語法糖能夠增加程序的可讀性,從而減少程序代碼出錯的機(jī)會。
3.處理reject()
在函數(shù)中添加 reject() 的調(diào)用,并在Promise對象后面追加一個 .catch() 的處理
var fs = require('fs')
var asyncReadFile = function(path) {
return new Promise(function(resolve, reject) {
fs.readFile(path, 'utf-8', function(err, data) {
if(err) {
reject(err)
}
resolve(data)
})
})
// This must be called in case of node-process terminated by reject()
.catch((err)=>{
return err
})
}
router.get('/', async function(req, res, next) {
var file = await asyncReadFile("./package.json")
console.log(file)
// 渲染
res.render('index', { title: 'Express' });
});
或者在調(diào)用的時候 catch 錯誤
var fs = require('fs')
var asyncReadFile = function(path) {
return new Promise(function(resolve, reject) {
fs.readFile(path, 'utf-8', function(err, data) {
if(err) {
reject(err)
}
resolve(data)
})
})
}
router.get('/', async function(req, res, next) {
try {
var file = await asyncReadFile("./package.json")
console.log(file)
}catch (e) {
console.error(e);
}
// 渲染
res.render('index', { title: 'Express' });
});
此外,需要注意的是,一旦 reject() 執(zhí)行,后面的代碼就立即停止了。
async function func() {
await Promise.reject(err);
await Promise.resolve(); // 不會執(zhí)行
}
4.多個await函數(shù)的并發(fā)
如果由多個await函數(shù)要一起執(zhí)行,且沒有先后關(guān)系,可以讓它們同時執(zhí)行,原型為:
var [f1, f2, ...] = await Promise.all([func1, func2, ...])
var [f1, f2] = await Promise.all([
asyncReadFile("./app.js"),
asyncReadFile("./package.json")
])
結(jié)果集中 f1 即 asyncReadFile("./app.js") 的返回值, f2 即 asyncReadFile("./package.js") 的返回值。