async/await 以及同步任务、异步任务、宏任务与微任务【附例题】

本文详细解析了JavaScript中的同步与异步任务执行机制,包括宏任务与微任务的区别及执行顺序,并通过多个例题帮助读者掌握async/await等异步编程概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.Javascript 中的同步任务和异步任务

同步任务: 可以立即执行不需要等待,例如 var a = 123

异步任务: 不能立即执行,需要等待一段时间后才能执行,例如 xhr网络请求 和 setTimeout定时器。

执行流程如下:

  1. 执行整个脚本,解析到同步和异步任务时分别放进不同的执行场所,同步任务放进主线程,异步任务放进事件表(Event Table)中并注册函数。

  2. 当指定的事情执行完成时,事件表(Event Table)会将这个函数移入事件队列(Event Queue)中等待执行。

  3. 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。

    不断重复整个过程,形成Event Loop(事件循环)。

举一个例子:

console.log('111') //同步任务 1
setTimeout(()=>{ //异步任务 1
    console.log('222')
},3000)
setTimeout(() => { // 异步任务 2
  console.log('333')
},  1000)
console.log('444') //同步任务 2
//答:111 444 333 222

对这个过程进行解析:

  1. 将同步任务1和2 放入主线程中,将异步任务1和2放入Event Table,并注册setTimeout的回调函数。
  2. 执行 主线程中的任务 按照从上到下的先后顺序 先输出 111 后 输出 444
  3. 当setTimeout定时结束后返回回调函数进入 Event Queue等待执行。
  4. 主线程执行完毕后从Event Queue读取setTimeout的回调函数放入主线程中并执行,因为两个异步任务执setTimeout执行时间不同,所以返回回调函数先后顺序也有所不同。

2.为什么异步任务中要设置队列(事件监听器)?

对于http、xhr等网络请求,执行的时间是不确定的,针对于这种情况设置了消息队列(事件监听器)。

事件监听器可以监听异步任务的状态,如果可以执行回调就会将相应的任务放入事件队列中。

举个例子:

假设两个网络请求

console.log('111') // 同步任务1
ajax().then(() => { // 异步任务1
  console.log('222')
})
ajax().then(() => { // 异步任务2
  console.log('333')
})
console.log('444') // 同步任务2

发起请求的两个异步任务,通过事件监听器来监听,如果异步任务2先返回回调函数,异步任务1后返回回调函数,那么就先执行异步任务2后执行异步任务1,答案则是:111 444 333 222。

再举个例子:
假设两个Promise

      console.log("111");
      const newPromise = new Promise((resolve, reject) => {
        resolve("hello world");
      }).then((res) => {
        console.log(222);
      });
      const newPromise1 = new Promise((resolve, reject) => {
        resolve("hello world");
      }).then((res) => {
        console.log(333);
      });
      console.log("444"); 

Promise是立即执行的同步函数,因此到resolve,都是同步的。但是.then()是异步的,他之间的回调会被放入微任务队列中等待执行。微任务和宏任务相关的知识在下面会讲。最后的结果是111 444 222 333

3.什么是宏任务和微任务?

一般情况,我们将异步任务又分为宏任务和微任务。

宏任务:由宿主(Node、浏览器等)发起,在微任务后运行,并会触发新一轮的 netxtTick()。

具体事件:

  • script (可以理解为外层同步代码)
  • setTimeout(定时器) / setInterval(计数器)
  • UI rendering / UI事件
  • postMessage,MessageChannel5. setImmediate,I/O(Node.js)

微任务:由JS引擎发起,在宏任务前运行,不会触发新一轮的 netxtTick()。

具体事件:

  • Promise (then catch finally)
  • MutaionObserver
  • Object.observe(已废弃;Proxy 对象替代)
  • process.nextTick(Node.js)

4.执行顺序

  • 说法1:执行同步代码 ==> 检查微任务并执行 ==> 执行宏任务1 ==> 检查微任务并执行 ==> 执行宏任务2 ==> 检查微任务并执行 ==> 执行宏任务3 …
  • 说法2:异步任务有宏任务和微任务两种,先将宏任务添加到宏任务队列中,将宏任务里面的微任务添加到微任务队列中,所有同步任务执行完后执行异步任务,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将宏任务从队列中调入主线程执行。这个过程一直循环执行(事件循环 Event loop)

一张比较易懂的图,可以配合着理解
在这里插入图片描述
2024-8-25补充:如果一个微任务在执行过程中创建了另一个微任务,新的微任务会被添加到当前微任务队列的末尾,并且会在下一轮事件循环内的宏任务执行前执行(也就是说在微任务进行过程中,如果出现了新的微任务,那么新的微任务就加在当前微任务队列末尾,在当前轮次进行处理(排队,赶个末班车))

4.async与await

4.1 async

async修饰符:async修饰的函数,默认返回 new Promise对象的resolve内容(若被async修饰的函数无返回值,则最终无返回值)。如此调用 async修饰的函数,可以直接使用then获取resolve值,用catch获取reject值

4.2 await(难点)

async方法内部,当程序执行到await方法时,会阻塞await方法后面的程序,进入await方法内部并执行到return前,然后跳出该async方法,执行与该async方法并列同步任务

await之后如果不是promise,await会阻塞后面的代码,会先执行async外面的同步代码,等外面的同步代码执行完成在执行async中的代码

如果它等到的是一个 promise 对象,await 也会暂停async后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。

例题1

async function async1() {
      console.log("async1 start"); //2
      await async2();
      console.log("async1 end"); // 6
    }

    async function async2() {
      console.log("async2"); //3
    }

    console.log("script start");  //1
    setTimeout(function () {
      console.log("setTimeout"); //8
    }, 0);

    async1();

    new Promise(function (resolve) {
      console.log("promise1");  //4
      resolve();
    }).then(function () {
      console.log("promise2"); //7
    });

    console.log("script end"); //5

解释:(下面异步宏任务和异步微任务略写为宏任务与微任务)

  1. 先按顺序执行同步任务 “script start”
  2. 遇见setTimeout,属于宏任务,放入到宏任务队列中
  3. 进入async1函数, 执行同步任务(语句),打印"async1 start"
  4. 遇见 await之后的async2 进入方法内部,直行到return前(因为async2里没有return,所以就全部执行完),打印"async2",然后await async2()后面的语句都被阻塞,放入到微任务队列中,继续执行与async2方法并列的同步任务
  5. 进入Promise,同步打印"promise1"
  6. 遇到.then,放入微任务队列中
  7. 执行同步任务"script end"
  8. 同步任务执行结束后,开始执行所有微任务,按顺序输出"async1 end",“promise2”
  9. 微任务执行结束后,输出下一轮宏任务"setTimeout"

例题2

    async function test1() {
      console.log("start test1"); //1
      console.log(await test2()); //7
      console.log("end test1"); //8
    }
    async function test2() {
      console.log("test2"); // 2
      return await "return test2 value";
    }
    test1();

    console.log("start async"); //3

    setTimeout(() => {
      console.log("setTimeout"); //9
    }, 0);

    new Promise((resolve, reject) => {
      console.log("promise1"); //4
      resolve();
    }).then(() => {
      console.log("promise2"); //6
    });

    console.log("end async"); // 5

解释:

  1. 执行同步任务"start test1"
  2. 先执行 await test2(),进入方法内部,执行到return前,打印"test2",然后挑出该async方法,阻塞后面的程序(个人理解:这一行因为没有return回来,所以也还没有执行完毕,所以 console.log(await test2()) 也没有打印)
  3. 继续执行同步任务"start async"
  4. 遇见setTimeout,放入宏任务队列
  5. promise里面同步执行"promise1"
  6. 遇见promise.then,放入微任务队列
  7. 执行同步任务"end async"
  8. test1外面的同步代码执行结束后,回到test1中,console.log(await test2())执行完成后返回Promise { “return test2 value”},是promise对象,推到微任务队列中
  9. 开始按顺序执行微任务队列,打印"promise2"、“return test2 value”、“end test1”
  10. 微任务队列完成后,执行下一轮宏任务,打印 “setTimeout”

2024.2.25补充:又看到两个挺不错的例题,补充进来

例题三

console.log('1')
setTimeout(function() {
    console.log('2')
    process.nextTick(function() {
        console.log('3')
    })
    new Promise(function(resolve) {
        console.log('4')
        resolve()
    }).then(function() {
        console.log('5')
    })
})

process.nextTick(function() {
    console.log('6')
})

new Promise(function(resolve) {
    console.log('7')
    resolve()
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9')
    process.nextTick(function() {
        console.log('10')
    })
    new Promise(function(resolve) {
        console.log('11')
        resolve()
    }).then(function() {
        console.log('12')
    })
})

解释
题目解释

例题四

new Promise(function (resolve) { 
    console.log('1')// 宏任务一
    resolve()
}).then(function () {
    console.log('3') // 宏任务一的微任务
})
setTimeout(function () { // 宏任务二
    console.log('4')
    setTimeout(function () { // 宏任务五
        console.log('7')
        new Promise(function (resolve) {
            console.log('8')
            resolve()
        }).then(function () {
            console.log('10')
            setTimeout(function () {  // 宏任务七
                console.log('12')
            })
        })
        console.log('9')
    })
})
setTimeout(function () { // 宏任务三
    console.log('5')
})
setTimeout(function () {  // 宏任务四
    console.log('6')
    setTimeout(function () { // 宏任务六
        console.log('11')
    })
})
console.log('2') // 宏任务一

解释
这道题挺恶心的,套来套去,不过难度不算大,动笔写一下画个流程图就行,答案就是1 2 3 … 11

2024-2-29 例题五

      const async1 = async () => {
        console.log("async1"); //2
        setTimeout(() => {
          console.log("timer1"); // 宏任务1
        }, 2000);

        await new Promise((resolve) => {
          console.log("promise1"); //3 await 阻塞后面的代码
        });

        // 因为没有Promise状态一直处于pending,没有变成resolve,所以下面两行不会执行
        console.log("async1 end");

        return "async1 success";
      };

      console.log("script start"); //1

      async1().then((res) => console.log(res));

      console.log("script end"); // 4

      Promise.resolve(1)
        .then(2)
        .then(Promise.resolve(3))
        .catch(4)
        .then((res) => console.log(res)); //5:1

      setTimeout(() => {
        console.log("timer2"); //宏任务2   虽然timer2排在timer1后面,但是timer2只要一秒。因此还是先输出timer2
      }, 1000);

		输出结果:
		script start
		async1
		promise1
		script end
		1
		timer2
		timer1

2024-8-24 例题六

	console.log(1);  
	setTimeout(() => { 
	  console.log(2); 
	  process.nextTick(() => { 
	    console.log(3); 
	  });
	  new Promise((resolve) => {
	    console.log(4); 
	    resolve();
	  }).then(() => {
	    console.log(5);
	  });
	});
	
	process.nextTick(() => {
	  console.log(6);
	});
	new Promise((resolve) => { 
	  console.log(7);  
	  resolve();
	}).then(() => { 
	  console.log(8);
	});
	setTimeout(() => { 
	  console.log(9);
	  process.nextTick(() => {
	    console.log(10); 
	  });
	}, 0);
	new Promise((resolve) => { 
	  console.log(11); 
	  resolve();
	}).then(() => { 
	  console.log(12);
	});

解释

console.log(1);
// 宏1
setTimeout(() => {
  console.log(2);
  process.nextTick(() => {
    console.log(3); // 微2-1
  });
  new Promise((resolve) => {
    console.log(4);
    resolve();
  }).then(() => {
    console.log(5); // 微2-2
  });
});
// 微1
process.nextTick(() => {
  console.log(6);
});

new Promise((resolve) => {
  console.log(7);
  resolve();
}).then(() => {
  console.log(8); // 微2
});
// 宏2
setTimeout(() => {
  console.log(9);
  process.nextTick(() => {
    console.log(10);
  });
}, 0);
new Promise((resolve) => {
  console.log(11);
  resolve();
}).then(() => {
  console.log(12); // 微3
});
console.log("111");
const newPromise = new Promise((resolve, reject) => {
  resolve("hello world");
}).then((res) => {
  console.log(222); // 微4
});
const newPromise1 = new Promise((resolve, reject) => {
  resolve("hello world");
}).then((res) => {
  console.log(333); // 微5
});
console.log("444");

答案:1,711,111,444,6,812,222,333(第一个宏任务),2,4,3,5(第二个宏任务),9,10(第三个宏任务)
这边需要注意的是,每一个宏任务执行结束时都会检查当前是否有可执行的微任务,如果有的话就都执行掉。所以说4后面紧跟着5

2024-8-25

Promise.resolve(1)
  .then(2) // 微1
  .then(Promise.resolve(3))
  .catch(4)
  .then((res) => console.log(res));

输出1

解释:

  • Promise.resolve(1): 这是一个立即被 resolved 的 Promise,值为 1。
  • 第一个 .then(2):通常,.then 接收两个参数:onFulfilled 和 onRejected,这两个参数通常是函数。
    如果传递的不是函数,比如这里传递的是数字 2,会被忽略,即默认地会直接传递上一个 Promise 的 resolved 值。因此,这个 .then 不会改变 Promise 的状态或值,Promise 仍然 resolved,值仍然是 1。
  • 第二个 .then(Promise.resolve(3)):同样地,.then 的参数需要是函数,而 Promise.resolve(3) 也是一个 Promise 对象,因此它会被忽略。Promise 继续保持 resolved 状态,值仍然是 1。
  • .catch(4): catch 方法用于捕获前面链条中的任何错误,但是前面的链条中没有抛出错误,因此不会进入catch回调里面,Promise 依然保持 resolved 状态,值仍然是 1。
  • 最后一个 .then((res) => console.log(res)): 这个 .then 终于接收到一个函数作为参数。因此它会将前面的 resolved 值 1 作为参数传递给这个函数。最后,console.log(res) 会输出 1。

2024-9-11

async function async1() {
  console.log("async1 start");
  let result = await async2();
  console.log(result);

  await new Promise((resolve) => {
    console.log("no resolve");
  });

  console.log("async1 end");
}
async function async2() {
  console.log("async2");
  return 1;
}
console.log("script start");
async1();
console.log("script end");

输出: script start, async1 start, async2, script end, 1, no resolve
解释:
这里最需要注意的就是await 一个async函数和 await一个Promise的区别。
1. 关于async函数,他是会隐式地包装一个Promise.resolve,因此即使你没有return一个值,他还是返回了一个Promise.resolve(undefined),而且状态是fulfilled。因此他之后的代码还是可以执行;
2. 关于Promise,如果他里面没有使用resolve或者reject函数,那么他的状态就一直是pending,就不会执行await之后的代码(像这题里面, async1 end就没有打印出来)
在这里插入图片描述

2024-9-16

有几个概念需要注意一下

  1. 在Promise里面直接return一个数,那么因为没有使用resolve或者reject,返回的不是一个Promise,因此也不能被下面的.then,.catch给捕获到;
async function P1(){
    console.log('P1');
    await new Promise((resolve,reject)=>{
        console.log('P1-1');
        return 12
    })
    console.log('P1 end');
}

P1();
// p1 end不会被输出
  1. 在.then()或者.catch()里面直接返回一个数,那么会被隐式地包装为Promise.resolve(aa),可以继续进行链式调用
new Promise((resolve, reject) => {
    resolve("Initial value");
})
.then(data => {
    console.log(data);  // 输出: "Initial value"
    return "Returned value";  // 普通值
})
.then(result => {
    console.log(result);  // 输出: "Returned value"
});

  1. 在async函数里面返回一个数,也会被隐式地包装为Promise.resolve(aa)
async function fetchData() {
    return "Data fetched"; // 返回普通字符串
}

fetchData().then(data => {
    console.log(data); // 输出: "Data fetched"
});

参考

async/await以及js中的微任务和宏任务

JS执行机制(同步、异步[宏任务、微任务])

一篇文章理解JS中同步任务和异步任务以及宏任务与微任务的原理和执行机制

JS中await、async、宏任务、微任务的执行顺序

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值