Node.js的异步编程风格
Node.js
是一个异步运行环境,异步意味着调用函数后结果不是立即返回,而是在未来某个时刻再通知给调用者。Node.js
使用最广泛的异步编程风格是通过回调函数来实现的。Node.js
可以使用以下几种异步编程风格:
- 回调函数
Promise
async/await
回调函数是最早出现的,也是最繁琐的,多个异步操作有顺序依赖时,会产生以下的代码:
func1.success(function(){
func2.success(function(){
func3.success(function(){
// do something
});
});
});
因此不推荐以回调函数的形式进行异步调用。
1. 回调函数
Node.js
异步编程是通过回调函数实现的,但是不能说使用回调函数就异步了,如下代码就不是异步的:
function test(callback) {
callback(1);
}
test(function(data) {
console.log(data);
});
Node.js
使用了大量的回调函数,几乎所有API都支持回调函数。由于调用接口存在失败的情况,而基于回调函数的编程无法使用标准JS中的抛出错误和捕获错误的方法,因此只能将错误对象作为回调函数的参数。Node.js
中回调函数的风格是统一的,异步函数的签名如下:
func(params, callback(error, data))
params
调用API的参数;callback(error, data)
回调函数。Node.js
中回调函数的第一个参数永远是Error
对象,之后才是回调成功的结果,如果没有出错,error=null
。
2. Promise
Promise
对象用于表示一个异步操作的最终完成(或失败)及其结果值。一个Promise
有以下几个状态:
pending
: 初始状态;fulfilled
: 操作成功;rejected
: 操作失败。
Promise
只会从pending
转换为fulfilled
或rejected
,整个转换只发生一次。Promise
构造函数接收一个执行函数,该函数接收resolve
和reject
两个回调函数,当执行函数运行成功时需调用resolve
,执行错误时需调用reject
。
Promise
构造函数的签名如下:
function Promise(function(resolve, reject): Promise{
// 异步逻辑
});
一旦Promise
发生状态变化,就会触发then
方法,then
方法签名如下:
Promise.prototype.then = function(onFulfilled[, onRejected]): Promise
onFulfilled Promise
:执行成功时回调;onRejected Promise
:执行失败时回调,可选参数;then
方法:返回一个新的Promise
对象,因此Promise
支持链式调用。
由于then
的第二个参数是可选的,因此Promise
的原型提供了catch
方法来捕获异步错误,catch
方法签名如下:
Promise.prototype.catch = function(onRejected): Promise
onRejected Promise
: 执行出错时回调;catch
方法:返回一个新的Promise
对象。
基本使用
let fs = require('fs');
function readFileAsync(path){
return new Promise(function(resolve, reject) {
fs.readFile(path, function(err, data) {
if (err) {
reject(err);
return ;
}
resolve(data);
});
});
}
readFileAsync('~/Desktop/data.txt')
.then(function(data) {
console.log(data);
}).catch(function(err){
console.log(err);
});
链式调用
Promise
的then
和catch
回调函数的返回值会作为下一个then
/catch
的输入参数,因此可通过链式Promise
来扁平化嵌套的回调函数。
// callback.js 回调风格
const fs = require('fs');
fs.readFile('~/Desktop/data.txt'. function(err, data){
if (err) {
console.log('读取data.txt出错', err);
return ;
}
console.log('data.txt读取成功', data);
fs.readFile("~/Desktop/data2.txt", function(err, data2) {
if (err){
console.log("读取data2.txt出错", err);
return ;
}
console.log('data2.txt读取成功', data2);
});
});
// Promise.js Promise风格
const fs = require('fs');
function readFileAsync(path){
return new Promise(function(resolve, reject) {
fs.readFile(path, function(err, data) {
if (err) {
reject(err);
return ;
}
resolve(data);
});
});
}
readFileAsync('~/Desktop/data.txt')
.then(function(data){
console.log('data.txt读取成功', data);
return readFileAsync('~/Desktop/data2.txt');
})
.then(function(data2){
console.log('data2.txt读取成功', data2);
})
.catch(function(err){
console.log("读取失败", err);
})
3. async / await
async
和await
关键字本质上是Promise
的语法糖,使得能够像同步代码一样异步编写,但是不会阻塞JS线程。还是上面的例子:
// async.js
const fs = require('fs');
function readFileAsync(path) {
return new Promise(function(resolve, reject) {
fs.readFile(path, function(err, data){
if (err) {
reject(err);
return ;
}
resolve(data);
});
});
}
aysnc function readFiles() {
try {
const data1 = await readFileAsync('~/Desktop/data.txt');
const data2 = await readFileAsync('~/Desktop/data2.txt');
console.log('文件1内容', data1);
console.log('文件2内容‘, data2);
}
catch(e) {
console.log('读取失败', e);
}
}
readFiles();
基本语法
(1) async
async
只能放在函数声明之前,支持普通函数、箭头函数和类函数。被修饰的函数不管返回什么值,最终都会返回Promise
,需要使用then
才可以获得Promise
的执行结果, 直接调用函数只会得到一个Promise。
- 函数返回基本值/空/或不带
then
方法对象时,Promise
的结果为该值,状态为fulfilled
。 - 函数抛出错误时,
Promise
状态为rejected
,reason
为抛出的错误对象。 - 函数本身返回一个
Promise
时,最终的Promise
结果为该Promise
的结果。
// 普通函数
async function doSomething(a, b) {
return a+b;
}
// 箭头函数
const doSomething2 = async (a, b) => {
return a + b;
};
// 类函数
class Demo {
async test (a, b) {
return a + b;
}
}
const data = doSomething(1, 2); // [object Promise]
doSomething(1, 2).then(function(result){
console.log(result); // 3
});
(2) await
await
只能在被async
修饰的函数内部调用,await
可以放在任何返回Promise
的函数前。在Promise
执行成功的情况下,await
语句将返回Promise
的成功值,否则await
将抛出错误,通过try
/catch
捕获即可。
示例:
function ajaxGet(url, timeout) {
return new Promise(function(resolve, reject){
const xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.timeout = timeout;
xhr.onload = function () {
resolve(xhr.responseText);
}
xhr.onerror = function(e){
reject(new Error("请求失败"));
};
xhr.ontimeout = function(){
reject(new Error("timeout"));
};
xhr.send(null);
});
}
async function getData(){
try {
const data = await adjaxGet('xx://a.json', 1000); // 1秒超时
console.log(data);
} catch (e){
console.error(e);
}
}
getData();
几乎所有的callback
类型的异步函数都可以使用Promise
进行包装。