前端常见面试题之异步(event loop, promise, async/await, 宏任务/微任务)

这篇具有很好参考价值的文章主要介绍了前端常见面试题之异步(event loop, promise, async/await, 宏任务/微任务)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、js如何执行

  • 从前到后,一行一行执行
  • 如果某一行执行报错,则停止下面代码执行
  • 先把同步代码执行完,再执行异步

示例:

console.log('开始');
setTimeout(function() {
  console.log('异步操作');
}, 2000);
console.log('结束');

输出结果为:

开始
结束
异步操作

可以看到,在执行异步操作的过程中,主线程不会等待异步操作结束,而是继续往下执行后续的代码,当满足条件时触发异步操作的回调函数。

异步代码不会阻塞后续代码的执行,它会在后台运行,并在特定条件满足时触发回调函数。常见的异步操作包括定时器事件监听AJAX 请求等

需要注意的是,JavaScript 采用事件循环(event loop)机制来处理异步代码的执行顺序。当主线程执行完同步代码后,会检查任务队列中是否还有异步任务,如果有,则按照优先级依次执行。当所有异步任务执行完毕后,事件循环会等待,直到有新的异步任务被添加到队列中才会继续执行。这就是 JavaScript 中异步执行的原理。

二、event loop过程

前端常见面试题之异步(event loop, promise, async/await, 宏任务/微任务),面试题,前端,promise,event loop,async/await,宏任务/微任务

  • 同步代码,一行一行放到Call Stack执行
  • 遇到异步,会先“记录”下,等待时机(定时、网络请求等)
  • 时机到了,就移动到Callback Queue
  • 如果Call Stack 为空(即同步代码执行完)Event Loop 开始工作
  • 轮询查找Callback Queue,如有则移动到Call Stack 执行
  • 然后继续轮询查找(永动机)

三、Promise

在JavaScript中,Promise是一种用于处理异步操作的对象。它可以表示一个尚未完成但最终会完成的操作,并提供了一种处理该操作结果的方式。

Promise有三种状态:pending(进行中)、resolved(已成功)和rejected(已失败)。最初处于pending状态,可以转换为resolved或rejected状态。一旦状态转变,就不会再改变。

Promise的主要用途是解决回调地狱(callback hell)问题,即多个异步操作依赖于前一个异步操作的结果,导致代码嵌套层级非常深。

以下是一个使用Promise的简单示例:

function getData() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      const data = 'Hello, World!';
      if (data) {
        resolve(data); // 将操作状态改为resolved,并将数据传递给下一个then方法
      } else {
        reject('Data not found'); // 将操作状态改为rejected,并传递错误信息给下一个catch方法
      }
    }, 2000);
  });
}

getData()
  .then(function(result) {
    console.log(result); // 打印数据:Hello, World!
    return result.length;
  })
  .then(function(length) {
    console.log(length); // 打印长度:13
  })
  .catch(function(error) {
    console.log(error); // 如果出错,则打印错误信息
  });

在上面的代码中,通过getData函数返回一个Promise对象。在Promise的构造函数中,通过setTimeout模拟一个异步操作,并在一段时间后模拟完成操作。在操作完成时,使用resolve方法将状态改为resolved,并将数据传递给第一个then方法。如果出现错误,则使用reject方法将状态改为rejected,并将错误信息传递给catch方法。

通过链式调用then方法,可以在每个then方法中依次处理操作结果。如果其中一个then方法返回了新的Promise对象,它将作为下一个then方法的输入。

最后,通过catch方法捕获任何错误,并在控制台中打印错误信息。

使用Promise可以更清晰地表达异步操作的逻辑,避免了回调地狱的问题,从而提高代码的可读性和可维护性。

1. 三种状态

  1. Pending:初始状态,表示promise的操作还未完成。不会触发then和catch
  2. Resolved:表示promise的操作已经成功完成。会触发后续的then函数
  3. Rejected:表示promise的操作失败或发生错误。会触发后续的catch函数

举例:

pending状态:创建一个新的Promise实例时,初始状态是pending。例如:

const promise = new Promise((resolve, reject) => {
    // do something
});

Resolved状态:当Promise实例成功地执行了resolve函数时,状态变为Resolved。例如:

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('Success');
    }, 2000);
});

promise.then((result) => {
    console.log(result);  // 输出:Success
});

rejected状态:当Promise实例执行了reject函数或者发生了错误时,状态变为rejected。例如:

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(new Error('Something went wrong'));
    }, 2000);
});

promise.catch((error) => {
    console.error(error);  // 输出:Error: Something went wrong
});

2. then和catch函数返回的状态

promise的then函数在正常情况下会返回一个新的promise对象,它的状态由then函数中的回调函数的返回值决定。如果回调函数返回一个普通的值,那么新的promise对象会变为已解决状态,并且其值就是回调函数的返回值。如果回调函数返回的是一个promise对象,那么新的promise对象的状态将跟随该promise对象的状态。catch函数也一样。

需要注意的是,如果then函数或catch函数中的回调函数本身抛出了异常,则新的promise对象的状态会变为已拒绝,并且该异常会被传递给下一个catch函数

const promise = new Promise((resolve, reject) => {
  resolve('Hello');
});

promise.then((result) => {
  throw new Error('Something went wrong');
}).catch((error) => {
  console.error(error.message); // 输出:Something went wrong
});

上面这个示例会输出‘Something went wrong’,那么问题来了,在catch后面再加上一个catch回调,加上的这个catch会执行吗?

const promise = new Promise((resolve, reject) => {
  resolve('Hello');
});

promise.then((result) => {
  throw new Error('Something went wrong');
}).catch((error) => {
  console.error(error.message); // 输出:Something went wrong
}).catch(() => {
  console.error('会不会执行呢?');
})

答案是第二个catch不会执行,控制台依然只打印
Something went wrong
前端常见面试题之异步(event loop, promise, async/await, 宏任务/微任务),面试题,前端,promise,event loop,async/await,宏任务/微任务
这是因为第一个catch函数返回的状态是resolved,不会触发后续的catch函数。

3. 看几道题

示例1

 Promise.reject().then(()=>{
          console.log('1')
      }).catch(()=>{
          console.log('2')
      }).then(()=>{
          console.log('3')
      })

执行这段代码会打印出以下内容:

2
3

这是因为Promise.reject()返回一个立即被拒绝的Promise对象。在第一个.then()中,由于Promise对象被拒绝,所以不会执行回调函数,然后会进入.catch(),打印出’2’。接着,第一个.catch()返回一个Promise对象,然后进入下一个.then(),打印出’3’。
注意:由于Promise.reject()直接触发了catch()方法,因此then()方法中的回调函数是不会执行的。

示例2

        Promise.resolve().then(() => {
          console.log("1");
          throw new Error("error1");
        }).catch(() => {
          console.log("2");
        }).then(() => {
          console.log("3");
        });

这段代码的执行完打印的顺序是:

1
2
3

首先,使用Promise.resolve()返回一个resolved状态的promise,然后执行.then()方法中的回调函数。在该回调函数中,首先打印"1",然后抛出一个错误"error1"。

接着,使用.catch()方法捕捉上一步中的错误。由于之前抛出了错误,所以会跳转到.catch()方法中的回调函数。在这个回调函数中,打印"2"。

最后,使用.then()方法执行回调函数,并打印"3"。

总结起来,即先执行.then()中的回调函数,再执行.catch()中的回调函数,最后执行下一个.then()中的回调函数。在使用Promise链式调用时,错误会被捕获并传递到接下来的.catch()中,不会中断链式调用。

示例3


      Promise.resolve().then(()=>{
          console.log('1')
          throw new Error('error1')
      }).catch(()=>{
          console.log('2')
      }).catch(()=>{
          console.log('3')
      })

执行完打印的顺序是:

1
2

原因是:首先,Promise.resolve()是一个已经完成的promise,所以then()方法会立即执行,并打印出1。然后,在then()方法的回调函数中,因为有一个throw语句,所以会抛出一个错误。接下来,catch()方法捕获到错误并执行相应的回调函数,打印出2。由于没有再出现错误,所以第二个catch()方法不会被执行。

四、async/await

1. 基本用法

async/await是JavaScript中处理异步操作的一种方式。它使得编写异步代码更加简洁和易于理解,同时解决了一些常见的异步编程问题。

使用async/await可以将异步操作的代码写成同步的风格,避免了回调地狱和链式调用的复杂性。它利用ES7(ECMAScript 2017)中引入的async函数和await表达式,使得异步代码的执行流程更加清晰。

举个例子,假设有一个需要异步获取数据的函数fetchData:

function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Data fetched!');
    }, 2000);
  });
}

在使用传统的Promise方法时,我们需要通过.then()来处理异步操作的结果:

fetchData()
  .then(data => {
    console.log(data);
    // 执行其他操作...
  })
  .catch(error => {
    console.error(error);
  });

使用async/await,可以将上述代码改成如下形式:

async function fetchData() {
  try {
    const data = await fetchData();
    console.log(data);
    // 执行其他操作...
  } catch (error) {
    console.error(error);
  }
}

在async函数内部,可以使用await关键字暂停函数的执行,等待Promise对象的状态变为resolved后再继续执行。通过使用try-catch语句,也能够轻松地捕获和处理异步操作可能抛出的错误。

总结一下,async/await主要作用有:

  1. 简化异步操作的代码结构,使其更易读和维护。
  2. 解决了回调地狱(callback hell)和链式调用的问题。
  3. 更容易捕获和处理异步操作的错误。

需要注意的是,使用async/await的代码需要在支持ES7的运行时环境中运行,或者通过Babel等工具进行转译。

2. async/await和Promise有什么关系

async/await 是 Promise 的一种语法糖,它建立在 Promise 的基础上,使异步的操作更加直观和易读。

通过使用 async 关键字来修饰一个函数,这个函数会自动返回一个 Promise 对象。在这个函数内部,可以使用 await 关键字来等待一个 Promise 对象的完成,并返回 Promise 的结果。

下面是一个使用 async/await 和 Promise 的示例:

function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Data fetched");
    }, 2000);
  });
}

async function displayData() {
  try {
    console.log("Fetching data...");
    const result = await fetchData();
    console.log(result);
    console.log("Data displayed");
  } catch (error) {
    console.log(error);
  }
}

displayData();

在上面的例子中,fetchData 函数返回一个 Promise 对象,并在 2 秒后通过 resolve 方法将结果设为 “Data fetched”。
displayData 函数使用了 async 关键字来标记为异步函数,并通过 await 等待 fetchData 函数的完成。
当 fetchData 函数完成后,返回的结果会赋值给 result 变量,并输出结果到控制台。
在 try-catch 块中,如果 Promise 被 reject,则会捕获到错误,并输出到控制台。

async/await 消除了 Promise 的嵌套回调,使异步代码的写法更加简洁和易读。同时,它可以结合其他 Promise 方法,如 Promise.all、Promise.race 等,来更好地管理和处理异步操作。

总结

1.执行async函数,返回的是Promise对象
2. await相当于Promise的then
3. try…catch可捕获异常,代替了Promise的catch

3. 看个题

        async function async1() {
            console.log('1')
            await async2()
            console.log('2')
        }
        async function async2() {
            console.log('3')
        }
        console.log('4')
        async1()
        console.log('5')

执行的顺序是:

4
1
3
5
2

这其实主要是await async2() 这里,只要是await后面的就相当于是异步的回调,只不过是用同步的写法来表示异步而已,如下:
前端常见面试题之异步(event loop, promise, async/await, 宏任务/微任务),面试题,前端,promise,event loop,async/await,宏任务/微任务

五、宏任务macroTask和微任务microTask

  • 宏任务:setTimeout, setInterval, Ajax, DOM事件
  • 微任务:Promise async/await
  • 微任务执行时机比宏任务要早

微任务是dom渲染前触发,宏任务是dom渲染后触发

看个例子

        // 事件循环:每次循环称为tick,每次tick的任务队列称为task queue
        // 1. 执行全局script
        // 2. 执行微任务
        // 3. 执行宏任务
        // 4. 渲染UI
        // 5. 执行下一个tick
        // 微任务是dom渲染前触发,宏任务是dom渲染后触发
        // 事件循环的顺序:先执行同步代码,再执行微任务,再执行宏任务
        console.log('1')
        setTimeout(() => {
            console.log('2')
            Promise.resolve().then(() => {
                console.log('3')
            })
        }, 0)

        Promise.resolve().then(() => {
            console.log('4')
            setTimeout(() => {
                console.log('5')
            }, 0)
        })
        console.log('6') // 1 6 4 2 3 5

这段代码的执行顺序如下:文章来源地址https://www.toymoban.com/news/detail-791428.html

  1. 执行 console.log('1'),打印出 1
  2. 执行 setTimeout(),将回调函数放入宏任务队列,并设置延迟为0秒
  3. 执行第一个 Promise.resolve().then(),将回调函数放入微任务队列
  4. 执行 console.log('6'),打印出 6
  5. 执行第一个微任务队列中的回调函数,打印出 4
  6. 执行第二个 setTimeout(),将回调函数放入宏任务队列,并设置延迟为0秒
  7. 执行宏任务队列中的回调函数,打印出 2
  8. 执行第二个微任务队列中的回调函数,打印出 3
  9. 执行第二个宏任务 setTimeout(),打印出 5

到了这里,关于前端常见面试题之异步(event loop, promise, async/await, 宏任务/微任务)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • vue async await promise 等待异步接口执行完毕再进行下一步操作

    需求:上传多个文件,每上传一个文件异步请求一下后台接口,并返回一个新文件名,等把所有的异步接口执行成功后,将上传已成功后新文件名数组得到再去更新业务数据 uni-file-picker 文件上传组件的配置 选择文件后,上传到服务器后端,一个一个的传,等异步执行完一下

    2024年02月12日
    浏览(50)
  • 【前端】浅谈async/await异步传染性

    \\\"异步传染性\\\"问题通常是指,当一个函数使用了async和await,其调用者也需要使用async和await处理异步操作,导致整个调用链都变成异步的。这种情况可能导致代码变得更复杂,不易维护。 类似于C# try catch的层层上抛,在某一层catch 查了很多资料 ,对于这个问题说法还都不一样

    2024年01月23日
    浏览(44)
  • Promise、Async/Await 详解

            Promise是抽象异步处理对象以及对其进行各种操作的组件。Promise本身是同步的立即执行函数解决异步回调的问题, 当调用 resolve 或 reject 回调函数进行处理的时候, 是异步操作, 会先执行.then/catch等,当主栈完成后,才会去调用执行resolve/reject中存放的方法。      

    2024年02月14日
    浏览(39)
  • async/await实现Promise.all()

    🐱个人主页: 不叫猫先生 🙋‍♂️作者简介:专注于前端领域各种技术,热衷分享,期待你的关注。 💫系列专栏:vue3从入门到精通 📝个人签名:不破不立 Promise.all() 方法接收一个 promise 的 iterable 类型(注:Array,Map,Set 都属于 ES6 的 iterable 类型)的输入,并且 只返回

    2024年01月18日
    浏览(44)
  • 彻底理解Promise和async/await

    1.异步行为是 为了优化因计算量大而时间长的操作 . 2.pedding 待定: 表示尚未开始或正在进行中    fulfilled 解决: 表示已经成功完成    rejected 拒绝: 表示没有完成 3.从pedding状态切换到fulfilled状态或rejected状态后,状态就不会再改变.而且也不能保证promise比如会脱离待定状态. 因此

    2024年02月03日
    浏览(48)
  • Promise, Generator, async/await的渐进理解

         作为前端开发者的伙伴们,肯定对Promise,Generator,async/await非常熟悉不过了。Promise绝对是烂记于心,而async/await却让使大伙们感觉到爽(原来异步可以这么简单)。可回头来梳理他们的关联时,你惊讶的发现,他们是如此的密切相关。       我对他们三者之间的关联

    2024年02月08日
    浏览(41)
  • ES6 Promise/Async/Await使用

    Promise应用 在工作中, 我们经常会遇到用异步请求数据, 查询一个结果, 然后把返回的参数放入到下一个执行的异步函数像这样: 当我们使用Promise后, 我们的程序就变成了这样: 控制台输出如下: async/await应用 看是不是简洁很多了, 如果你不想使用这种链式调用, 也可以结合async/

    2024年02月12日
    浏览(43)
  • 深入学习JavaScript系列(七)——Promise async/await generator

    本篇属于本系列第七篇 第一篇:#深入学习JavaScript系列(一)—— ES6中的JS执行上下文 第二篇:# 深入学习JavaScript系列(二)——作用域和作用域链 第三篇:# 深入学习JavaScript系列(三)——this 第四篇:# 深入学习JavaScript系列(四)——JS闭包 第五篇:# 深入学习JavaScrip

    2023年04月08日
    浏览(48)
  • ES6中Promise、Async/await解决回调地狱、Proxy代理

    1.Promise 作为一些场景必须要使用的一个对象,比如说我们要发送一个请求,但是在发送这个请求之前我们需要以另一个请求返回的结果中的一个数据作为这次请求的参数,也就是说这个请求必须在另一个请求后面,当然我们用setTimeout定时器写一个延时函数也可以,但是当有

    2024年02月12日
    浏览(33)
  • HarmonyOS通过async与await同异步转换 解决异步回调地狱

    我在 HarmonyOS 发送http网络请求 中讲述了 HTTP请求的基本方式 然后 就带出了 回调地狱的问题 然后 上文 HarmonyOS 通过Promise 解决异步回调地狱问题 我们用Promise的解决方案 搞定了 这个问题 但是 Promise 这种写法 可读性其实没有那么优秀 没有搞定 Promise return规则的人甚至都看不懂

    2024年01月24日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包