在JavaScript中,我们一直难以解决两个与异步相关的问题。一个是Node.js中被称为“回调地狱”的东西。我们很容易就掉入深层嵌套回调函数的陷阱,每个嵌套都会使代码复杂化,让错误和结果的处理变得更加困难。但JavaScript语言并没有为程序员提供正确表达异步执行的方式。
于是,出现了一些第三方库,它们承诺可以简化异步执行。这是另一个通过隐藏复杂性带来更多复杂性的例子。
const async = require(‘async’); const fs = require(‘fs’); const cat = function(filez, fini) { async.eachSeries(filez, function(filenm, next) { fs.readFile(filenm, ‘utf8’, function(err, data) { if (err) return next(err); process.stdout.write(data, ‘utf8’, function(err) { if (err) next(err); else next(); }); }); }, function(err) { if (err) fini(err); else fini(); }); }; cat(process.argv.slice(2), function(err) { if (err) console.error(err.stack); });这是个模仿Unix cat命令的例子。async库非常适合用于简化异步执行顺序,但同时也引入了一堆模板代码,从而模糊了程序员的意图。
这里实际上包含了一个循环,只是没有使用循环语句和自然的循环结构。此外,错误和结果的处理逻辑被放在了回调函数内。在Node.js采用ES 2015和ES 2016之前,我们只能做到这些。
Node.js 10.x中,等价的代码是这样的:
const fs = require(‘fs’).promises; async function cat(filenmz) { for (var filenm of filenmz) { let data = await fs.readFile(filenm, ‘utf8’); await new Promise((resolve, reject) => { process.stdout.write(data, ‘utf8’, (err) => { if (err) reject(err); else resolve(); }); }); } } cat(process.argv.slice(2)).catch(err => { console.error(err.stack); });这段代码使用async/await函数重写了之前的逻辑。虽然异步逻辑是一样的,但这次使用了普通的循环结构。错误和结果的处理也显得很自然。这样的代码更容易阅读,也更容易编码,程序员的意图也更容易被理解。
唯一的瑕疵是process.stdout.write没有提供Promise接口,因此用在异步函数中时需要丢Promise进行包装。