前后端交互-Node.js异步编程
1 - 同步API,异步API
同步API:通过返回值拿到结果,只有当前API执行完成后,才能继续执行下一个API。
异步API:通过回调函数拿到结果,当前API的执行不会阻塞后续代码的执行。
2 - 同步API,异步API的区别( 获取返回值 )
同步API可以从返回值中拿到API执行的结果,但是异步API是不可以的。
// 同步
function sum (n1, n2) {
return n1 + n2;
}
const result = sum (10, 20);
// 异步
function getMsg () {
setTimeout(function () {
return { msg: 'Hello Node.js' }
}, 2000);
}
const msg = getMsg (); // 这里拿不到返回值
3 - 回调函数
自己定义函数让别人去调用,类似于OC中的block回调。
// 1.参数是个函数
function getData (callback) {
callback && callback(); //3.调用回调函数
}
// 2.传入函数
getData (() => {});
4 - 使用回调函数获取异步API执行结果
// 1.参数是个函数
function getMsg (callback) {
setTimeout(function () {
callback && callback ({ msg: 'Hello Node.js' }) //3.调用回调函数
}, 2000);
}
// 2.传入函数
getMsg (function (msg) {
console.log(msg);
});
5 - 同步API,异步API的区别(代码执行顺序)
同步API从上到下依次执行,前面代码会阻塞后面代码的执行。
for (var i = 0; i < 100000; i++) {
console.log(i);
}
console.log('for循环后面的代码');
异步API不会等待API执行完成后再向下执行代码。
console.log('代码开始执行');
setTimeout(() => { console.log('2秒后执行的代码')}, 2000);
setTimeout(() => { console.log('"0秒"后执行的代码')}, 0);
console.log('代码结束执行');
// 打印:代码开始执行 代码结束执行 0s后执行的代码 2s后执行的代码
6 - 代码执行顺序分析
- 先执行同步api的代码
- 再把异步代码api放入异步代码执行区域
- 当异步代码的回调函数可以执行的时候,就把回调函数放入同步代码执行区,继续执行
console.log('代码开始执行');
setTimeout(() => {
console.log('2秒后执行的代码');
}, 2000);
setTimeout(() => {
console.log('"0秒"后执行的代码');
}, 0);
console.log('代码结束执行');
// 打印:代码开始执行 代码结束执行 0s后执行的代码 2s后执行的代码
7 - Node.js中的异步API
fs.readFile('./demo.txt', (err, result) => {});
var server = http.createServer();
server.on('request', (req, res) => {});
如果异步API后面代码的执行依赖当前异步API的执行结果,但实际上后续代码在执行的时候异步API还没有返回结果,这个问题要怎么解决呢?
fs.readFile('./demo.txt', (err, result) => {});
console.log('文件读取结果');
// 可以把这行代码,写到回调函数里面,但是这样会有回调地狱的问题
8 - 如何解决回调地狱的问题
如果有需求:依次读取A文件、B文件、C文件,如果进行函数嵌套实现,会有回调地狱的问题,怎么解决呢?
1. 使用Promise解决(ES6新增)
Promise出现的目的是解决Node.js异步编程中回调地狱的问题。
我们使用new来构建一个Promise,Promise的构造函数接收一个参数,是函数,并且传入两个参数: resolve,reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
if (true) {
resolve({name: '张三'})
// 在里面通过resolve把成功的结果传出去
} else {
reject('失败了')
// 在里面通过reject把失败的结果传出去
}
}, 2000);
});
//在外面通过调用.then传入一个函数从而拿到成功的结果,通过调用.catch传入一个函数拿到失败的结果
promise.then(result => console.log(result); // {name: '张三'}
.catch(error => console.log(error); // 失败了
Promise使用举例,代码如下:
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)
// })
// })
// });
// 使用Promise解决回调地狱的问题
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()返回的是个Promise对象,它会自动调用下一个then,然后把结果放到 then 的参数里面
p1().then((r1)=> { // 这里的r1就是读取文件1的返回值
console.log(r1);
return p2(); // 返回的还是一个Promise对象
})
// 上个Promise对象会自动调用then,然后把结果放到 then 的参数里面
.then((r2)=> { // 这里的r2就是读取文件2的返回值
console.log(r2);
return p3(); // 返回的还是一个Promise对象
})
// 上个Promise对象会自动调用then,然后把结果放到 then 的参数里面
.then((r3) => { // 这里的r3就是读取文件3的返回值
console.log(r3)
})
① 对象方法: .then() .catch() .finally()
对象方法是通过Promise实例对象调用的。
- .then():得到异步任务正确的结果
- .catch():获取异常信息
- .finally():成功与否都会执行(不是正式标准)
<script type="text/javascript">
/*
Promise常用API-对象方法
*/
// console.dir(Promise);
function foo() {
return new Promise(function(resolve, reject){
setTimeout(function(){
// resolve(123);
reject('error');
}, 100);
})
}
// 下面两种写法是等效的
// -------------第一种写法,错误信息写在.then的第二个参数-------------
//foo()
// .then(function(data){
// # 得到异步任务正确的结果
// console.log(data)
// },function(data){
// # 获取异常信息
// console.log(data)
// })
// # 成功与否都会执行(不是正式标准)
// .finally(function(){
// console.log('finished')
// });
// -------------第二种写法,错误信息写在.catch里面,推荐-------------
foo()
.then(function(data){
console.log(data)
})
.catch(function(data){
console.log(data)
})
.finally(function(){
console.log('finished')
});
</script>
② 类方法: .all() race()
类方法是直接通过Promise调用的。
-
.all():并发处理多个异步任务,所有任务都执行完成才能得到结果。
Promise.all
方法接受一个数组作参数,数组中的对象(p1、p2、p3)均为promise实例(如果不是一个promise,该项会被用Promise.resolve
转换为一个promise),它的状态由这三个promise实例决定,所有任务都执行完成才能得到结果。 -
.race():并发处理多个异步任务,只要有一个任务完成就能得到结果。
Promise.race
方法同样接受一个数组作参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilled
或rejected
),p的状态就跟着改变,并把第一个改变状态的promise的返回值传给p的回调函数,其他任务仍在执行,但是返回值被丢弃。
<script type="text/javascript">
/*
Promise常用API-类方法
*/
// console.dir(Promise)
function queryData(url) {
return new Promise(function(resolve, reject){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState != 4) return;
if(xhr.readyState == 4 && xhr.status == 200) {
// 处理正常的情况
resolve(xhr.responseText);
}else{
// 处理异常情况
reject('服务器错误');
}
};
xhr.open('get', url);
xhr.send(null);
});
}
var p1 = queryData('http://localhost:3000/a1');
var p2 = queryData('http://localhost:3000/a2');
var p3 = queryData('http://localhost:3000/a3');
Promise.all([p1,p2,p3]).then(function(result){
// all 中的参数 [p1,p2,p3] 和 返回的结果一一对应[" HELLO TOM", "HELLO JERRY", "HELLO SPIKE"]
console.log(result) // ["HELLO TOM", "HELLO JERRY", "HELLO SPIKE"]
})
Promise.race([p1,p2,p3]).then(function(result){
// 由于p1执行较快,Promise的then()将获得结果'P1'。p2,p3仍在继续执行,但执行结果将被丢弃。
console.log(result) // "HELLO TOM"
})
</script>
2. 使用异步函数解决(ES7新增)
异步函数是异步编程语法的终极解决方案,它是基于promise对象上的一层封装,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了。
// 匿名函数
const fn = async () => {};
// 命名函数
async function fn () {}
① async关键字
- 普通函数定义前加async关键字,普通函数变成异步函数,异步函数默认返回promise对象。
- 在异步函数内部使用return关键字进行结果返回,结果会被包裹在promise对象中,return关键字代替了resolve方法。在异步函数内部使用throw关键字抛出程序异常,throw关键字代替了reject方法。
- 调用异步函数再链式调用then方法获取异步函数执行结果,调用catch方法获取异步函数执行的错误信息
② await关键字
异步函数中还可以使用await关键字
- await关键字只能出现在异步函数中
- await promise,await后面只能写promise对象,写其他类型的API是不可以的
- await关键字可以暂停异步函数的执行,等待promise对象返回结果后再向下执行函数
使用await关键字,依次读取A文件、B文件、C文件,示例代码如下:
const fs = require('fs');
// 改造现有异步函数api 让其返回promise对象 从而支持异步函数语法
const promisify = require('util').promisify;
// 调用promisify方法改造现有异步API 让其返回promise对象
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();