用async/await改造Node.js(Express)网站

用async/await改造Node.js(Express)网站

Mike的读书季IP属地: 北京
2018.11.02 00:13:00字数 582阅读 3,115

1.回调的嵌套陷阱

在Node.js中,使用回调的方式进行异步操作,我们以读取文件内容为例:

const fs = require('fs');

// 定义一个以回调的方式获取文件的函数
function asyncReadFile(path, callback) {
    fs.readFile(path, 'utf-8', function(err, data) {
        callback(err,data)
    })
}

// 调用:
router.get('/', async function(req, res, next) {
    asyncReadFile("./package.json", (err, data) {
        console.log(data)
        // 渲染页面
        res.render('index', { title: 'Express' });
    })
})

从上面的调用不难推测出,以回调的方式来实现异步,嵌套将会是开发者的噩梦:

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' });
            })
        })
    })
})

在业务查询比较多、需要同时触发的任务中,回调会严重影响排版及阅读,尤其对接手人(甚至开发者自己)的阅读和理解造成了很大的困难。

2.使用async/await

接下来,我将改造上面的 asyncReadFile() 函数,以将返回值其构造为 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 是成对出现的。即你要在哪个函数使用 await 的方式,就应当对这个函数进行 async 声明。

事实上,这个声明是一种语法糖,即在不改变语法的基础上,让代码的可读性更好、更不容易出错。async就是Generator函数的语法糖。

Generator函数的用法是这样的:

var gen = function* (){
  var f1 = yield readFile('/etc/fstab');
  var f2 = yield readFile('/etc/shells');
};

对应的async用法则是:

var gen = async function (){
  var f1 = await readFile('/etc/fstab');
  var f2 = await readFile('/etc/shells');
};

即将 * 和 yield 分别用 async 和 await 替换了。

语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

3.处理reject()

在函数中添加 reject() 的调用,并在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' });
});

或者在调用的时候 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() 执行,后面的代码就立即停止了。

async function func() {
  await Promise.reject(err);
  await Promise.resolve(); // 不会执行
}

4.多个await函数的并发

如果由多个await函数要一起执行,且没有先后关系,可以让它们同时执行,原型为:

var [f1, f2, ...] = await Promise.all([func1, func2, ...])
var [f1, f2] = await Promise.all([
    asyncReadFile("./app.js"),
    asyncReadFile("./package.json")
])

结果集中 f1 即 asyncReadFile("./app.js") 的返回值, f2 即 asyncReadFile("./package.js") 的返回值。


参考资料:
《ECMAScript6笔记:异步操作和Async函数》

posted on 2024-04-07 17:55  漫思  阅读(38)  评论(0编辑  收藏  举报

导航