5.1 同步API,异步API
- 举例:
console.log('before');
setTimeout(function() { //异步API
console.log('last');
}, 2000);
console.log('after');
- 运行
- 同步API:只有当当前代码执行完成后才继续执行下一个API
- 异步API:当前API代码的执行不会阻塞后续代码的执行
5.2 同步API与异步API的区别(返回值)
第一端代码可以得到返回值,下面思考第二段代码可以拿到返回值吗?
function getMsg() {
setTimeout(function() {
return {
msg: 'Hello node.js'
}
}, 2000)
return undefined;
};
const msg = getMsg();
console.log(msg);//undefined
异步API中,无法通过异步API的返回值拿到执行结果的,异步API的执行结果只能通过回调函数的方式得到
5.3 回调函数
- 引子
//定义
function getData(callback) {
callback('123')
}
//调用(将一个函数作为另一个函数的参数)
getData(function(n) {
console.log('callback函数被调用了');
console.log(n);
})
运行后
- 实例
function getMsg(callback) {
setTimeout(function() {
callback({ msg: 'Hello node.js' });
}, 2000)
};
const msg = getMsg(function(data) {
console.log(data);
});
- 运行后
5.5 同步API与异步API的区别(代码执行顺序)
同步API从上到下依次执行,前面代码会阻碍后面代码的执行
for (var i = 0; i < 10; i++) {
console.log(i);
}
console.log('for循环后面的代码');
异步API不会等待API执行完成后再向下执行代码
console.log('代码开始执行');
setTimeout(function() {
console.log('2s');
}, 2000);
setTimeout(function() {
console.log('0s');
}, 0);
console.log('代码结束执行');
代码执行顺序分析
5.6 nodejs中的异步API
解决上述问题可以将console.log()放在回调函数里面。但是这种方法有时候会出现问题:比如如果想要依次读取A、B、C文件。可以将console.log('A')放在回调函数中,之后要在读取A文件的回调函数中利用fs.readFile读取B文件,在B文间的回调函数中利用fs.readFile读取C文件,这样就在回调函数中出现了fs.readFile的嵌套。
演示
(1)在async文件夹下新建文件1.txt、2.txt、3.txt文件内容分别是1、2、3
(2)在async文件夹下新建js文件6.callbackhell.js
(3)错误格式如下:
const fs = require('fs');
fs.readFile('./1.txt','utf8',(err,result1)=>{
console.log(result1);
});
fs.readFile('./2.txt','utf8',(err,result2)=>{
console.log(result2);
});
fs.readFile('./3.txt','utf8',(err,result3)=>{
console.log(result3);
});
(4)错误原因分析
- 第一个文件的读取是需要时间的,时间由文件大小而定,开发人员并不知道
- 在第一个文件读取完成之后再去读取2文件,再读3文件呢?(依次)
- 只有系统在调用第一个文件的回调函数的时候才可以说明第一个文件读取成功
(5)正确写法
const fs = require('fs');
fs.readFile('./1.txt', 'utf8', (err, result1) => {
console.log(result1);
fs.readFile('./2.txt', 'utf8', (err, result2) => {
console.log(result2);
fs.readFile('./3.txt', 'utf8', (err, result3) => {
console.log(result3);
});
});
});
(6)运行结果
虽然可行但是会出现回调地狱的问题
5.7 Promise
Promise是一个构造函数用于解决nodejs异步编程中回调地域的问题
将异步API的执行以及错误的处理进行了分离
- 举例1
const fs = require('fs');
//Promise构造里面传递的参数是一个匿名函数
//匿名函数有两个参数resolve, reject
//resolve, reject也都是函数
let promise = new Promise((resolve, reject) => {
fs.readFile('./1.txt', 'utf8', (err, result) => {
if (err != null) {
//异步API执行失败:文件读取失败就调用reject方法
reject(err); //err是实参
} else {
//异步API执行成功:文件读取成功就调用result方法
resolve(result); //resutl是实参
}
});
});
//promise对象下有一个then和catch方法,参数是一个匿名函数
//当异步API执行成功的时候调用resolve函数,实际上就是在调用then方法里面的函数
//当异步API执行成功的时候调用reject函数,实际上就是在调用catch方法里面的函数
promise.then((result) => { //resutl是形参
console.log(result);
}) //链式编程
.catch((err) => { //err是形参
console.log(err);
});
-
运行
-
举例2(解决回调地狱问题)
const fs = require('fs');
function p1() {
return new Promise((resolve, reject) => {
fs.readFile('./1.txt', 'utf8', (err, result) => {
resolve(result);
});
});
};
function p2() {
return new Promise((resolve, reject) => {
fs.readFile('./2.txt', 'utf8', (err, result) => {
resolve(result);
});
});
};
function p3() {
return new Promise((resolve, reject) => {
fs.readFile('./3.txt', 'utf8', (err, result) => {
resolve(result);
});
});
}
//错误写法,以下不是依次执行
// p1().then((r1) => {
// console.log(r1);
// });
// p2().then((r2) => {
// console.log(r2);
// });
// p3().then((r3) => {
// console.log(r3);
// });
//正确写法
p1().then((r1) => {
console.log(r1);
return p2();
}) //下一个then里面可以拿到上一个then里面的promise对象
.then((r2) => {
console.log(r2);
return p3();
})
.then((r3) => {
console.log(r3);
});
- 运行
这样就解决了异步编程的回调地狱问题,但是代码过于繁琐,下面使用异步函数让代码简化
5.8 异步函数
异步函数是es7是新增语法,就是在基于promise函数的基础上进行了一次封装
在普通函数前加上async关键字就会变成异步函数
- 1
//1. 在普通函数定义的前面加上async关键字 普通函数就会变成异步函数
//2. 异步函数默认的返回值是promise对象,而不是undefined
async function fn() {
}
console.log(fn());
- 2
async function fn() {
return 123;
}
console.log(fn());
- 3
async function fn() {
return 123;
}
// fn().then(function(data) {
// console.log(data);//123
// });
//或者
fn().then((data) => {
console.log(data); //123
});
如上发现return关键字可以替代resolve方法,如果异步函数内部的API执行出错了呢,如何将错误信息返回到函数的外面呢?答:可以利用throw关键字。
//1. 在普通函数定义的前面加上async关键字 普通函数就会变成异步函数
//2. 异步函数默认的返回值是promise对象,而不是undefined
//3. 在异步函数内部使用throw关键字进行错误抛出
async function fn() {
throw '发生了一些错误';
return 123;
}
// fn().then(function(data) {
// console.log(data);//123
// });
//或者
fn().then((data) => {
console.log(data); //123
}).catch(function(err) {
console.log(err);
});
- await关键字
//await关键字
async function p1() {
return 'p1';//Promise { 'p1' }
};
async function p2() {
return 'p2';
};
async function p3() {
return 'p3';
};
//保证上述3个异步函数依次执行
async function run() {
//错误写法
// p1()//Promise { 'p1' }
// p2()
// p3()
//正确写法
let r1 = await p1();
let r2 = await p2();
let r3 = await p3();
console.log(r1);
console.log(r2);
console.log(r3);
};
run();
async函数无论在函数体中的函数的返回值是什么,返回的都是promise对象,若没有写返回值那么就返回Promise { undefined }
await可以将Promise { xxx }变为xxx
利用异步函数依次读取3个文件
const fs = require('fs');
//promisify方法用于改造nodejs中的现有异步API的,让异步API返回promise对象从而去支持异步函数语法
//用promisify方法对fs.readFile方法进行包装,那么fs.readFile的返回值就一定是promise对象
const promisify = require('util').promisify; //util模块下的promisify方法
const readFile = promisify(fs.readFile);
async function run() {
let r1 = await readFile('./1.txt', 'utf8')
let r2 = await readFile('./2.txt', 'utf8')
let r3 = await readFile('./3.txt', 'utf8')
console.log(r1);
console.log(r2);
console.log(r3);
}
run();
- 运行
- 以下是错误写法
- 错误原因在于忽视了异步函数的返回值是promise对象
//await关键字
async function p1() {
return fs.readFile('./1.txt', 'utf8', (err, result) => {
if (err != null) {
throw '发生了一些错误';
return;
}
return result;
});
//Promise { undefined } 异步函数的返回值是promise对象
};
async function p2() {
return fs.readFile('./2.txt', 'utf8', (err, result) => {
if (err != null) {
throw '发生了一些错误';
return;
}
return result;
});
};
async function p3() {
return fs.readFile('./3.txt', 'utf8', (err, result) => {
if (err != null) {
throw '发生了一些错误';
return;
}
return result;
});
};
//保证上述3个异步函数依次执行
async function run() {
let r1 = await p1()
let r2 = await p2()
let r3 = await p3()
let r11 = r1
.then(function(data) {
console.log(data);
}).catch(function(err) {
console.log(err);
});
let r22 = r2
.then(function(data) {
console.log(data);
}).catch(function(err) {
console.log(err);
});
let r33 = r3
.then(function(data) {
console.log(data);
}).catch(function(err) {
console.log(err);
});
console.log(r11);
console.log(r22);
console.log(r33);
};
run();