[toc]
# 異步編程
## 哪些代碼是異步代碼?
異步代碼有兩類:`微任務代碼` 和 `宏任務代碼`。
- 微任務(microtask)
process.nextTick(node獨有),
Promises.then,
MutationObserver
- 宏任務(macrotask):
AJAX、
setTimeout,
setInterval,
setImmediate(node獨有)、
事件回調(鼠標鍵盤事件等)、
I/O
## 代碼的執行順序
同步 -> 異步 ( 微任務 -> 宏任務)
示例代碼、
```
// 同步 1
console.log('a')
// 宏任務 5
setTimeout(() => {
console.log('b')
})
// 同步 2
console.log('c')
// 微任務 4
Promise.resolve().then(() => console.log('r'))
// 同步 3
console.log('e')
// 最終的輸出結果:acerb
```
## 如何將異步代碼封裝成 Promise 對象?
代碼演示:
```
// 有一段異步代碼
setTimeout(()=>{
console.log('hello')
})
// 把上面的異步代碼封裝成 Promise 對象
// 1. new Promise 對象
const p = new Promise(function(resolve, reject){
// 2. 把異步代碼放到里面
setTimeout(()=>{
console.log('hello')
// 3. 在異步代碼執行結束之后改變 Promise 的狀態
if (成功) {
// 如果成功就調用 resolve 方法設置為 fulfilled 狀態
resolve(成功返回的數據)
} else {
// 如果失敗就調用 reject 方法設置為 rejected 狀態
reject(返回的錯誤信息)
}
})
})
```
## Promise 是干什么用的?為什么要用?
異步代碼需要使用回調函數,當需要按順調用多個異步代碼時,就必須一層一層的嵌套回調函數,這樣的代碼比較麻煩比較不好維護,這種多層嵌套的回調函數我們稱之為 `回調地獄`:
~~~
// 連續執行4段異步代碼,需要一層一層“向內套”
setTimeout(()=>{
console.log(1)
setTimeout(()=>{
console.log(2)
setTimeout(()=>{
console.log(3)
setTimeout(()=>{
console.log(4)
})
})
})
})
~~~
為了避免編寫回調地獄的代碼,ES6 中提供了 Promise 來解決這個問題:把異步代碼封裝成 Promise 對象之后,代碼會變成 `扁平` 而不是 `層層向里套`:
~~~
// 1、把異步代碼封裝成 Promise 對象:
let p1 = function() {
return new Promise((resolve,reject)=>{
// 異步代碼
setTImeout(()=>{
console.log(1)
resolve()
})
})
}
let p2 = function() {
return new Promise((resolve,reject)=>{
// 異步代碼
setTImeout(()=>{
console.log(2)
resolve()
})
})
}
let p3 = function() {
return new Promise((resolve,reject)=>{
// 異步代碼
setTImeout(()=>{
console.log(3)
resolve()
})
})
}
let p4 = function() {
return new Promise((resolve,reject)=>{
// 異步代碼
setTImeout(()=>{
console.log(4)
resolve()
})
})
}
// 2. 依次調用(避免回調地獄)
p1().then(()=>{
return p2()
}).then(()=>{
return p3()
}).then(()={
return p4()
})
~~~
## async ... await ... 是干什么用的?如何用?
Promise 雖然可以解決回調地獄的問題,但是還需要使用 .then 、.catch ,還是比較麻煩,所以為了進一步優化異步代碼,ES8 在 Promise 的基礎上又添加了 async/await 語法,可以 `以同步的方式編寫異步代碼`。
示例代碼、
~~~
// 上面封裝好的 Promise 對象,可以直接以下面的方式調用,
// 下面的函數 p1 執行完之后 ,才執行 p2,然后p3,然后p4
// await 必須要用在 async function 中
// await 的返回值就是異步代碼成功時的返回值
// async 的返回值又是一個新的 Promise 對象
const p = async function(){
await p1()
await p2()
await p3()
await p4()
}
// p 還是一個 Promise 對象
p().then(()=>{console.log('完成'})
~~~