Node.js回调地狱及使用Promiseasync和await函数的解决方法
Posted 橘猫吃不胖~
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Node.js回调地狱及使用Promiseasync和await函数的解决方法相关的知识,希望对你有一定的参考价值。
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)
// 创建promise对象
let p = new Promise(function (resolve, reject)
// 处理异步任务
let flag = true;
setTimeout(function () // 模拟异步调用
if (flag) // 模拟异步调用成功
resolve(str); // 将成功的str通过resolve传递出去
else // 模拟异步调用失败
reject("操作失败"); // 将失败信息传递出去
)
)
return p;
let temp = fn(1);
// 接收resolve传递的信息
temp.then(data => // data=1
console.log(data);
return fn(2);
).then(data => // data=2
console.log(data);
return fn(3);
).then(data => // data=3
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)
// time毫秒后执行resolve
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)
// 创建promise对象
let p = new Promise(function (resolve, reject)
// 处理异步任务
let flag = true;
setTimeout(function () // 模拟异步调用
if (flag) // 模拟异步调用成功
resolve(str); // 将成功的str通过resolve传递出去
else // 模拟异步调用失败
reject("操作失败"); // 将失败信息传递出去
)
)
return p;
async function test()
// await直接拿到fn()返回的promise的数据,并且赋值给s1
let s1 = await fn(1);
let s2 = await fn(2);
let s3 = await fn(3);
console.log(s1, s2, s3); // 1 2 3
test();
await等待的虽然是promise对象,但不必写.then(…),直接可以得到返回值。
let sleep = function (time)
return new Promise(function (resolve, reject)
// time毫秒后执行resolve
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)
// 创建promise对象
let p = new Promise(function (resolve, reject)
// 处理异步任务
let flag = true;
setTimeout(function () // 模拟异步调用
if (flag) // 模拟异步调用成功
resolve(str); // 将成功的str通过resolve传递出去
else // 模拟异步调用失败
reject("操作失败"); // 将失败信息传递出去
)
)
return p;
async function test()
try
// await直接拿到fn()返回的promise的数据,并且赋值给s1
let s1 = await fn(1);
let s2 = await fn(2);
let s3 = await fn(3);
console.log(s1, s2, s3); // 1 2 3
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捕捉
以上是关于Node.js回调地狱及使用Promiseasync和await函数的解决方法的主要内容,如果未能解决你的问题,请参考以下文章