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区别

  1. promise是ES6,async/await是ES7
  2. async/await相对于promise来讲,写法更加优雅
  3. reject状态:
    1. promise错误可以通过catch来捕捉,建议尾部捕获错误
    2. async/await既可以用.then又可以用try-catch捕捉

以上是关于Node.js回调地狱及使用Promiseasync和await函数的解决方法的主要内容,如果未能解决你的问题,请参考以下文章

Node.js 中的回调地狱

Node.JS + Mongoose 回调地狱

告别回调地狱,在Node里优雅的访问MySQL

Node.js服务器开发(下)

如何使用async.js简化Node.js中的回调代码

把 Node.js 中的回调转换为 Promise