async/await
异步函数 async function
async关键字用于声明一个异步函数:
- async是asynchronous单词的缩写,异步、非同步;
- sync是synchronous单词的缩写,同步、同时;
async异步函数可以有很多中写法:
async function foo1() {
}
const foo2 = async () => {
}
class Foo {
async bar() {
}
}
异步函数的执行流程
异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行。
异步函数的返回值一定是一个 Promise,但是 return 后面的值会被包裹到 Promise.resolve 中
所以 return 不同的值会对返回的 Promise 请求成功与否造成影响
- 如果 return Promise 对象,返回的 Promise.resolve 的状态会由 return 后面的 Promise 决定;
- 如果 return 后面是一个对象并且实现了thenable,那么会由对象的 then 方法来决定; ```javascript async function foo1() { console.log(‘foo function start~’) console.log(‘foo function end~’) return 123 // return 普通值 }
async function foo2() { console.log(‘foo function start~’) console.log(‘foo function end~’) return new Promise(resolve => { // return promise resolve(‘promise success’) }) }
async function foo3() { console.log(‘foo function start~’) console.log(‘foo function end~’) return { // return thenable then(resolve, reject) { resolve(‘thenable success’) } } }
console.log(‘script start’)
foo1().then(res => { console.log(res) }) foo2().then(res => { console.log(res) }) foo3().then(res => { console.log(res) })
console.log(‘script end’)
// script start
// foo function start~
// foo function end~
// foo function start~
// foo function end~
// foo function start~
// foo function end~
// script end
// 123
// thenable success
// promise success
异步函数中的异常, 会被作为异步函数返回的 Promise 的 reject 值
```javascript
async function foo() {
console.log("foo function start~")
console.log("中间代码~")
// 异步函数中的异常, 会被作为异步函数返回的Promise的reject值的
throw new Error("error message")
console.log("foo function end~")
}
// async 是个语法糖,会让异步函数的返回值一定是一个Promise
foo().catch(err => {
console.log("coderwhy err:", err)
})
console.log("后续还有代码~~~~~")
// foo function start~
// 中间代码~
// 后续还有代码~~~~~
// coderwhy err: Error: error message
await 关键字
async函数另外一个特殊之处就是可以在它内部使用await关键字,而普通函数中是不可以的。
await关键字有什么特点呢?
- 通常使用 await 是后面会跟上一个表达式,这个表达式会返回一个 Promise,或者表达式就是 Promise;
- 当 await 后面的 promise 状态为 fulfilled 时,await 会将 promise 的请求结果,也就是 resolve(res) 中的 res 返回。换句话说 await 是后面 promise 中 resolve(res) 的语法糖。
- 此时整个异步函数返回的 promise 没有什么意义,返回的promise.then 拿不到await 后面的 promise 的结果
- 并且 await 只有等到 Promise 的状态变成 fulfilled 状态,并获取请求成功的值之后,才继续执行下面的异步函数;
- await 下面的代码就像是定义在 then 函数中,因为要等到 await 后面表达式返回的 promise 得出正确结果才能执行。但是别忘记了 await 当前这一行和之前的代码都是同步执行的。 ```javascript // request.js function requestData(url) { return new Promise((resolve, reject) => { setTimeout(() => { // 拿到请求的结果,这里 url 就是请求结果 resolve(url) }, 2000) }) }
async function getData() { const res1 = await requestData(‘hhh’) //效果和之前生成器函数中通过yield得到请求的结果一样 console.log(res1); //但是这里不用通过next().value.then()这样嵌套了,直接就像定义在了then中
const res2 = await requestData(res1 + ‘ tatakai’) console.log(res2); }
getData()
// Alan // Alan tatakai
如果 await 后面是一个普通的值,那么会直接返回这个值;<br />如果 await 后面是一个 thenable 的对象,那么会根据对象的 then 方法调用来决定后续的值;
如果 await 后面的表达式,返回的 Promise 是 reject 的状态,那么会停止执行异步函数,并将这个 reject 结果直接**作为整个异步函数的 Promise 的 reject 值**;
```javascript
// await 跟普通值,直接返回
async function foo1() {
const res = await 123
console.log(res)
}
foo1() // 123
// 跟 thenable 对象
async function foo2() {
const res = await {
then(resolve) {
resolve('Alan')
}
}
console.log(res)
}
foo2() // Alan
// await 后面的 promise 请求失败
async function foo3() {
const result = await new Promise((resolve, reject) => {
reject('failure') // await 后面的 promise 失败
})
console.log(result + '请求失败') // 不会执行,只有请求成功才会往下执行
}
foo3().catch(err => { // 整个异步函数的 promise.catch 捕捉到错误
console.log(err)
})
// failure
事件循环
基础知识
进程和线程
操作系统 – 进程 – 线程
操作系统的工作方式
浏览器中的JavaScript线程
浏览器的事件循环
浏览器执行的过程
正常情况下,执行上下文进入执行上下文栈,同步代码一行一行往下执行,执行完毕就出栈。
如果同步代码往下执行,执行到 setTimeout 函数,setTimeout 也会被加入到执行栈中,并且执行完会出栈。但是 setTimeout 和普通函数不一样,它带有延迟的回调函数。在执行的时候,setTimeout 会将回调函数加入一个等待队列中,等全局同步代码执行完,回调函数就会被加入到执行栈中进行执行。
整个这个过程就构成了一轮循环:
同步代码开始执行 —> 进入执行上下文栈 —> 部分代码进入等待队列 —> 同步代码执行完 —> 等待队列中代码进入执行上下文栈,成为新的同步代码,开始下一轮执行。
js 同步执行线程 —— 执行栈 —— 等待队列 —— js 同步执行线程
宏任务和微任务
练习
setTimeout(function () {
console.log("setTimeout1");
new Promise(function (resolve) {
resolve();
}).then(function () {
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then4");
});
console.log("then2");
});
});
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("then1");
});
setTimeout(function () {
console.log("setTimeout2");
});
console.log(2);
queueMicrotask(() => {
console.log("queueMicrotask1")
});
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then3");
});
setTimeout(function () { // 1. 加入宏任务队列,待执行
console.log('setTimeout1') // 9. 进入同步队列立即执行
new Promise(function (resolve) { // 10. 执行器立即执行
resolve()
}).then(function () { // 11. then 加入微任务队列,待执行
new Promise(function (resolve) { // 12. 执行器立即执行
resolve()
}).then(function () { // 13. then4 添加到微任务,待执行
console.log('then4') // 15. 清空微任务队列,执行 then4
})
console.log('then2') // 14. 立即执行,整个 then 执行完毕
})
})
new Promise(function (resolve) {
console.log('promise1') // 2. Promise 执行器回调函数立即执行,加入同步任务立即执行
resolve() // 3. resolve 转到 then 函数
}).then(function () { // 3.1 then 加入微任务队列,待执行
console.log('then1')
})
setTimeout(function () { // 4. 加入宏任务队列,待执行
console.log('setTimeout2') // 16. 清空宏任务,加入同步任务立即执行
})
console.log(2) // 5. 同步任务立即执行
queueMicrotask(() => { // 6. 微任务函数,加入微任务队列,待执行
console.log('queueMicrotask1')
})
new Promise(function (resolve) { // 7. 加入同步任务立即执行
resolve()
}).then(function () { // 8. then 加入微任务队列,待执行
console.log('then3')
})
// 同步任务: promise1 2
// 微任务:then1 queueMicrotask then3
// 宏任务:setTime(1) setTime(2)
// 同步任务执行完,清空微任务
// 结果:promise1 2 then1 queueMicrotask1 then3
// 清空宏任务
// 同步任务:setTimeout1
// 微任务:then
// 宏任务:setTime(2)
// setTime(1) 中添加了微任务,执行任何一个宏任务之前都得清空微任务
// 所以 setTime(2) 暂时没执行,执行 then
// 同步任务:then2
// 微任务:then4
// 宏任务:setTime(2)
// 再次清空微任务队列,执行宏任务
// 同步任务:then4 setTimeout2
// 微任务:
// 宏任务:
// 结果:promise1 2 then1 queueMicrotask1 then3 setTimeout1 then2 then4 setTimeout2
async function bar() {
console.log('22222')
return new Promise(resolve => {
resolve()
})
}
async function foo() {
console.log('111111')
await bar()
console.log('33333')
}
foo()
console.log('444444')
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')
特别注意:异步函数返回 promise 对象的情况,这时 await 下面的‘then’,其实存在嵌套,需要加入下一轮的微任务队列。
async function bar() { // 1. 定义异步函数,待执行
console.log('22222') // 6. 同步执行
return new Promise(resolve => { // 7. 返回 promise 对象,这个要特别注意
resolve() // 7.2 请求成功,将调用 then 方法
})
}
async function foo() { // 2. 定义异步函数,待执行
console.log('111111') // 4. 异步函数内部代码同步执行
await bar() // 5. 同步执行 bar 异步函数 // 7.1 虽然 await 获取 promise 请求结果
// 但是其实 await 不是直接获取的 7.2 中的resolve,中间有一个嵌套
// bar函数的返回值准确应该是:Promise.resolve(new Promise())
// 只是为了方便我们平时就直接看成 await 直接获取了 7.2 的 resolve 结果
// 但是执行顺序上就不能省略这个嵌套直接看。8 这一行的 then 实际应该是 then(then())
// 所以 33333 是放入了下一轮的微任务中
console.log('33333') // 8. await 之下代码为请求成功后执行,相当于 then,
// 8.1 then(then(33333))放入的微任务队列
// 21. then(33333) 放入微任务队列
}
foo() // 3. 执行异步函数,加入同步队列
console.log('444444') // 9. 同步执行
async function async1() { // 10. 异步函数定义,待执行
console.log('async1-start') // 15. 立即执行
await async2() // 16. 同步执行 async2 异步函数 // 17.2 undefined 也就是请求成功但没有数据
console.log('async1-end') // 18. 请求成功,‘then’ 放入微任务,待执行
}
async function async2() { // 11. 异步函数定义,待执行
console.log('async2') // 17. 立即执行 // 17.1 没有return,相当于 return undefined
}
console.log('script-start') // 12. 同步代码立即执行
setTimeout(function () { // 13. 定时器加入宏任务
console.log('setTimeout') // 23. 立即执行
}, 0)
async1() // 14. 执行异步任务,加入同步队列
new Promise(function (resolve) {
console.log('promise1') // 19. 立即执行
resolve()
}).then(function () { // 20. 放入微任务
console.log('promise2')
})
console.log('script-end') // 22. 立即执行
// 同步任务:111111 22222 444444 script-start async1-start async2 promise1 script-end
// 微任务:then(then(33333) async1-end promise2
// 宏任务:setTimeout
// 清空微任务,执行宏任务
// 同步任务:async1-end promise2
// 微任务:then(33333)
// 宏任务:setTimeout
// 清空微任务,执行宏任务
// 同步任务:33333 setTimeout
// 微任务:
// 宏任务:
// 结果:111111 22222 44444 script-start async1-start async2 promise1
// script-end async1-end promise2 33333 setTimeout
Promise.resolve().then(() => {
console.log(0);
// 1.直接return一个值 相当于resolve(4)
// return 4
// 2.return thenable的值
return {
then: function(resolve) {
// 大量的计算
resolve(4)
}
}
// 3.return Promise
// 不是普通的值, 多加一次微任务
// Promise.resolve(4), 多加一次微任务
// 一共多加两次微任务
return Promise.resolve(4)
}).then((res) => {
console.log(res)
})
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(5);
}).then(() =>{
console.log(6);
})
Node 的事件循环
Node 架构
浏览器中的 EventLoop 是根据 HTML5 定义的规范来实现的,不同的浏览器可能会有不同的实现,而 Node 中是由 libuv 库实现的。
这里我们来给出一个Node的架构图:
- 我们会发现libuv中主要维护了一个EventLoop和worker threads(线程池);
- EventLoop负责调用系统的一些其他操作:文件的IO、Network、child-processes等
libuv 是一个多平台的专注于异步IO的库,它最初是为Node开发的,但是现在也被使用到 Luvit、Julia、pyuv 等其他地方;
application 的 js 代码给 V8 进行编译执行,其中一些需要依靠操作系统进行操作的地方,就会通过 bindings 传递给 libuv 来完成。
libuv 里面主要有事件队列和线程池,并且维护了他们之间的事件循环。那么这个循环是怎么来的?谁构成了循环?
阻塞式 IO 和 非阻塞式 IO
总结:js 调用操作系统进行 IO 操作,操作系统提供两种调用方式:阻塞式和非阻塞式,node 主要是基于非阻塞式。
非阻塞式 IO 的问题
总结:libuv 为了获取非阻塞式 IO 的结果,就会开启线程去轮询,并为此维护了一个线程池。一旦某个线程获取到 IO 结果,就会将 js 中读取结果的回调函数放入任务队列中等待执行。之后事件循环启动告知 js 主线程来执行任务队列中的函数。
阻塞和非阻塞,同步和异步的区别?
Node 事件循环的阶段
因为一个 tick 分为多个阶段,所以每个 tick 循环,node 都维护了多个任务队列。而浏览器只维护了两个队列,一个宏任务一个微任务。
Node 的宏任务和微任务
我们会发现从一次事件循环的Tick来说,Node的事件循环更复杂,它也分为微任务和宏任务:
- 宏任务(macrotask):setTimeout、setInterval、IO事件、setImmediate、close事件;
- 微任务(microtask):Promise的then回调、process.nextTick、queueMicrotask;
宏任务和微任务也是分布在队列中,所以也存在微任务队列和宏任务队列之分:
微任务队列:
- next tick queue:process.nextTick;
- other queue:Promise的then回调、queueMicrotask;
宏任务队列:
- timer queue:setTimeout、setInterval;
- poll queue:IO事件;
- check queue:setImmediate;
- close queue:close事件;
node 队列也有宏任务和微任务队列之分,那肯定队列之间也有执行顺序,并且微任务与微任务队列之间,宏任务与宏任务队列之间也有顺序:
- next tick microtask queue;
- other microtask queue;
- timer queue;
- poll queue;
- check queue;
- close queue;
总体上,微任务优先宏任务毋庸置疑,具体到函数,微任务中最高的是process.nextTick
函数,然后是其他微任务函数,宏任务中最快的是 定时器函数,其次是 IO 函数,setImmediate
函数,最后是 close 事件宏任务。
所以简单总结一下:**process.nextTick**
> 普通微任务 > 定时器 > IO 函数 >**setImmediate**
> close 事件
setTimeout(回调函数, 0)、setImmediate(回调函数)执行顺序分析
会出现两种情况:
- 情况一: setTimeout、setImmediate
- 情况二:setImmediate、setTimeout
练习
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout0')
}, 0)
setTimeout(function () {
console.log('setTimeout2')
}, 300)
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick1'));
async1();
process.nextTick(() => console.log('nextTick2'));
new Promise(function (resolve) {
console.log('promise1')
resolve();
console.log('promise2')
}).then(function () {
console.log('promise3')
})
console.log('script end')
async function async1() {
console.log('async1-start') // 7. 立即执行
await async2() // 8. 立即执行 async2 函数
console.log('async1-end') // 8.2 加入普通微任务
}
async function async2() {
console.log('async2') // 8.1 立即执行
}
console.log('script-start') // 1. 同步立即执行
setTimeout(function () { // 2. 定时器宏任务
console.log('setTimeout0')
}, 0)
setTimeout(function () { // 3. 定时宏任务,但是会延迟 3 秒,故最后放入定时器宏任务
console.log('setTimeout2')
}, 3000)
setImmediate(() => console.log('setImmediate')); // 4. check 宏任务队列
process.nextTick(() => console.log('nextTick1')); // 5. 最快的微任务
async1(); // 6. 执行异步函数
process.nextTick(() => console.log('nextTick2')); // 9. 加入最快微任务队列
new Promise(function (resolve) {
console.log('promise1') // 10. 立即执行
resolve(); // 11. 执行 then
console.log('promise2') // 12. 立即执行
}).then(function () { // 11.1 then 放入普通微任务,待执行
console.log('promise3')
})
console.log('script-end') // 13. 立即执行
// 同步队列:script-start async1-start async2 promise1 promise2 script-end
// 微任务:nextTick1 nextTick2
// 普通微任务:async1-end promise3
// 定时器宏任务:setTimeout0 setTimeout2
// IO 宏任务:
// check 宏任务:setImmediate
// 3 秒后,定时器函数进入定时器宏任务队列
// 同步队列:nextTick1 nextTick2 async1-end promise3 setTimeout0 setImmediate
// 微任务:
// 普通微任务:
// 定时器宏任务:setTimeout2
// IO 宏任务:
// check 宏任务:
// 结果:script-start async1-start async2 promise1 promise2 script-end
// nextTick1 nextTick2 async1-end promise3 setTimeout0 setImmediate setTimeout2