谈谈Javascript 中 promise、async和await, setTimeout的执行顺序

Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。

JS调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。

Javascript单线程任务被分为同步任务异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。

JavaScript中,任务被分为两种,一种宏任务(MacroTask),一种叫微任务(MicroTask)。

MacroTask(宏任务)

  • script全部代码、setTimeoutsetInterval

MicroTask(微任务)

  • Promise、ssync/await

执行栈在执行完同步任务后,查看执行栈是否为空,如果执行栈为空,就会去检查微任务队列是否为空,如果为空的话,就执行宏任务,否则就一次性执行完所有微任务。
每次单个宏任务执行完毕后,检查微任务队列是否为空,如果不为空的话,会按照先入先出的规则全部执行完微任务后,设置微任务队列为null,然后再执行宏任务,如此循环。

注意:

  • setTimeout属于宏任务
  • Promise本身是同步的立即执行函数,Promise.then属于微任务
  • async方法执行时,遇到await会立即执行表达式,表达式之后的代码放到微任务执行
  • Promise优先于setTimeout宏任务。所以,setTimeout回调会在最后执行。
  • Promise一旦被定义,就会立即执行。
  • Promiserejectresolve是异步执行的回调。所以,resolve()会被放到回调队列中,在主函数执行完和setTimeout前调用。
  • await执行完后,会让出线程。async标记的函数会返回一个Promise对象

所以,以下程序代码输出的打印顺序为:

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('asnyc1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(() => {
    console.log('setTimeOut');
}, 0);
async1();
new Promise(function (reslove) {
    console.log('promise1');
    reslove();
}).then(function () {
    console.log('promise2');
})
console.log('script end');

输出: 

script start
async1 start
async2
promise1
script end
asnyc1 end
promise2
setTimeOut

 具体的执行步骤细节:

  1. console.log('script start')输出:script start
  2. setTimeout被放在最后调用
  3. 执行async1函数,输出async1 start。然后,进入async2函数,输出async2,并返回Promise对象。回到async1,由于await,让出线程,async2函数返回的Promise放在回调队列
  4. 新new了一个Promise对象,输出promise1。其中的resolve()被放在回调队列。
  5. console.log('script end')输出:script end
  6. 执行回调队列中,async1返回的Promise对象,对象产生的resolve被放入对调队列。这里不输出任何值。
  7. 执行回调队列中,由于async1函数返回的promise对象的resolve,输出async1 end
  8.  执行回调队列中,下方Promise显式声明的resolve,输出promise2
  9. 执行回调队列中,最后的setTimeout,输出setTimeout
  10. finish

讨论: 在新版本中,都是先执行async1 end再执行promise2。 但是在以前的node版本中,会先执行promise2再执行async1 end。

 

### JavaScriptPromiseasync await 的关系及用法 #### 一、Promise 基础 `Promise` 对象代表了一个异步操作的最终完成(或失败)及其结果值。它有三种状态:待定(pending),已实现(fulfilled)被拒绝(rejected)[^3]。 创建 `Promise` 实例通常是为了封装一些耗时的操作,比如网络请求或者文件读取: ```javascript const eatBreakfast = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve('吃早饭'); }, 1000); }); }; ``` 此代码片段展示了如何通过返回一个新的 `Promise` 来表示一个简单的异步动作——模拟花费一秒时间来“吃早餐”。当这个过程结束时,调用 `resolve()` 方法传递成功的结果给后续链上的回调函数[^4]。 #### 二、使用 then() 处理 Promise 结果 一旦有了 `Promise`,就可以利用 `.then()` 方法注册成功的处理程序以及可选的错误处理器`.catch()`: ```javascript eatBreakfast().then(message => console.log(message)).catch(error => console.error(error)); // 输出 "吃早饭" ``` 这段代码会在 “吃早饭”的承诺兑现之后打印消息;如果过程中发生任何异常,则会被捕获并记录下来[^2]。 #### 三、引入 async/await 提升可读性维护性 为了改善传统基于回调的方式所带来的“回调地狱”,即多层嵌套难以管理的情况,ES8 引入了 `async` `await` 关键词作为更优雅地编写异步逻辑的方法之一。 - **Async Function**: 定义带有 `async` 关键字前缀的函数意味着该函数内部可以安全地使用 `await` 表达式,并且总是隐含地返回一个 `Promise`。 - **Await Expression**: 当在一个 `async` 函数体内遇到 `await` 操作符时,执行流程将会暂停在此处直至对应的 `Promise` 被解决或拒收为止,在这期间不会阻塞其他任务继续运行[^1]。 下面是一个改进版的例子,展示怎样运用这些特性让复杂的异步序列看起来像是顺序执行的一样直观易懂: ```javascript async function dailyRoutine() { try { let breakfastMessage = await eatBreakfast(); console.log(breakfastMessage); // 这里还可以加入更多类似的异步活动... } catch (error) { console.error(`遇到了一个问题: ${error}`); } } dailyRoutine(); ``` 在这个例子中,`dailyRoutine` 是一个异步函数,其中包含了对另一个异步方法 `eatBreakfast` 的调用。由于用了 `await`,所以即使实际发生了延迟,“吃早饭”这条语句也会按照预期顺序执行完毕后再往下走,从而大大提高了代码的清晰度易于理解的程度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值