Node.js回调地狱及使用Promise、async和await函数的解决方法
1 什么是回调函数
回调函数:当一个函数作为参数传入到另外一个函数,并且该函数不会立即执行;当满足某个条件时才执行该函数。
下面代码中的fn就是回调函数:
function fn() {
console.log("橘猫吃不胖");
}
setTimeout(fn, 1000);
2 同步任务与异步任务
同步任务:在主线程队列中,只有前一个任务完成后才会执行下一个任务
异步任务:不进入主线程队列,而是进入异步队列,前一个任务完成与否不影响后一个任务的执行(不阻塞后续任务执行的任务)
示例代码:
setTimeout(function () {
console.log("执行了回调函数");
}, 1000);
console.log("橘猫吃不胖");
如果按照代码编写的顺序,应该先输出“执行了回调函数”,然后输入“橘猫吃不胖”。但是实际输出为:
3 什么是回调地狱
回调地狱:在回调函数中再嵌套回调函数的情况称为回调地狱(是实现代码顺序执行的一种操作方式)。
示例代码:
setTimeout(function () {
console.log(1);
setTimeout(function () {
console.log(2);
setTimeout(function () {
console.log(3);
}, 1000);
}, 1000);
}, 1000);
依次输出1、2、3
回调地狱问题:①代码可读性差、可维护性差;②代码的扩展性差;
回调地狱的解决方法:①promise对象;②async和await函数;
4 Promise对象
4.1 概述
Promise对象是一个原生的JavaScript对象,是一种异步编程的解决方案,可以替换掉传统的回调函数解决方案。
它通过一个回调,避免更多的回调。简单说Promise就是一个容器,里面保存着某个未来才会结束的事件 (通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。
Promise有3个状态:pending(初始)、resloved(成功)、rejected(失败)。
Promise 状态发生改变,就会触发.then()里的响应函数处理后续步骤。 Promise 状态一经改变,不会再变。 Promise 实例一经创建,执行器立即执行。
注意: 1、Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject,它们是两个函数,又是JavaScript引擎提供,不是自己部署。 resolve函数的作用:将Promise对象的状态从“未完成”变成“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去; reject函数的作用:在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
2、Promise对象的then方法用来接收处理成功时响应的数据,catch方法用来接收处理失败时相应的数据。
3、Promise的链式编程可以保证代码的执行顺序,前提是每一次在then做完处理后,一定要return一个Promise对象,这样才能在下一次then时接收到数据。
示例代码:
function fn(str) {
let p = new Promise(function (resolve, reject) {
let flag = true;
setTimeout(function () {
if (flag) {
resolve(str);
} else {
reject("操作失败");
}
})
})
return p;
}
let temp = fn(1);
temp.then(data => {
console.log(data);
return fn(2);
}).then(data => {
console.log(data);
return fn(3);
}).then(data => {
console.log(data);
}).catch(err => {
console.log(err);
});
依次输出1、2、3
4.2 all的用法
Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。用Promise.all来执行,all接收一个数组参数,里面的值最终都返回Promise对象。例如,三个异步操作的并行执行的,等到它们都执行完后才会进到then里面。all会把所有异步操作的结果放进一个数组中传给then
示例代码:
function getWidth() {
return new Promise((resolve, reject) => {
setTimeout(resolve(5), 1000);
})
}
function getHeight() {
return new Promise((resolve, reject) => {
setTimeout(resolve(4), 1000);
})
}
Promise.all([getWidth(), getHeight()]).then(result => {
console.log("Result:", result);
})
Result: [ 5, 4 ]
4.3 race的用法
顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
示例代码:
function getWidth() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 10000, 1);
})
}
function getHeight() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 2000, 2);
})
}
function getLength() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 3);
})
}
Promise.race([getLength(), getHeight(), getLength()]).then(result => {
console.log("Result:", result);
})
程序结果:Result: 3
all和race的区别:
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。
需要特别注意的是:Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。这样最大的好处是:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。
Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
5 async和await函数
5.1 概述
Promise虽然跳出了异步嵌套的怪圈,用链式表达更加清晰,但是我们也发现如果有大量的异步请求的时候,流程复杂的情况下,会发现充满了屏幕的then,看起来非常吃力,而ES7的async/await的出现就是为了解决这种复杂的情况。
async用来修饰函数:表示函数是一个异步函数 await用来修饰函数:表示等待被修饰的函数的运行结果出来后,再执行后续的操作。必须在async修饰的函数中使用,不能单独使用。
注意: A、可以直接获取Promise对象的resolve传递的信息,不需要使用.then B、使用try…catch来捕获Promise对象的reject传递的异步操作失败的信息
示例:实现一个暂停功能,输入N毫秒,则停顿N毫秒后才继续往下执行
let sleep = function (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve();
}, time);
})
}
let start = async function () {
console.log(1);
await sleep(1000);
console.log(2);
}
start();
结果为:先输出1,然后1秒后输出2
5.2 使用async/await基本规则
- await关键字只能在使用async定义的函数中使用
- ?await后面可以直接跟一个 Promise实例对象(可以跟任何表达式,更多的是跟一个返回Promise对象的表达式)
- await函数不能单独使用
- await可以直接拿到Promise中resolve中的数据。
5.3 示例
将4.1中的示例使用async/await进行优化,代码如下:
function fn(str) {
let p = new Promise(function (resolve, reject) {
let flag = true;
setTimeout(function () {
if (flag) {
resolve(str);
} else {
reject("操作失败");
}
})
})
return p;
}
async function test() {
let s1 = await fn(1);
let s2 = await fn(2);
let s3 = await fn(3);
console.log(s1, s2, s3);
}
test();
await等待的虽然是promise对象,但不必写.then(…),直接可以得到返回值。
let sleep = function (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve("Ok");
}, time);
})
}
let start = async function () {
let result = await sleep(3000);
console.log(result);
}
start();
既然.then(…)不用写了,那么.catch(…)也不用写,可以直接用标准的try…catch语法捕捉错误。
function fn(str) {
let p = new Promise(function (resolve, reject) {
let flag = true;
setTimeout(function () {
if (flag) {
resolve(str);
} else {
reject("操作失败");
}
})
})
return p;
}
async function test() {
try {
let s1 = await fn(1);
let s2 = await fn(2);
let s3 = await fn(3);
console.log(s1, s2, s3);
} catch (err) {
console.log(err);
}
}
test();
5.4 promise和async/await区别
- promise是ES6,async/await是ES7
- async/await相对于promise来讲,写法更加优雅
- reject状态:
- promise错误可以通过catch来捕捉,建议尾部捕获错误
- async/await既可以用.then又可以用try-catch捕捉
|