我們處理異步的方式,從開始的回調(diào),到Promise,再到現(xiàn)在的async,await,變得越來越方便,直觀了。但是知其然要知其所以然,所以我們一步步來分析他們是如何實(shí)現(xiàn)的(需要知道Promise的使用方法,await async的使用方法,以及generator的功能)。
Promise
function process() {
setTimeout(() => {
console.log("timeout")
}, 1000)
}
如果我們想要執(zhí)行上面的函數(shù),并且在計(jì)時(shí)結(jié)束后的回調(diào)中執(zhí)行某些動(dòng)作,應(yīng)該怎么辦呢?
我們可以使用一個(gè)回調(diào):
function process(callback) {
setTimeout(() => {
console.log("timeout")
callback()
}, 1000)
}
process(function doSomething() {
console.log("do something")
})
這是我們正常的回調(diào)方式,但是這樣的方式不夠靈活,而且當(dāng)需要嵌套回調(diào)的時(shí)候,往往就寫成了callback hell。Promise其實(shí)就是對(duì)上面的process進(jìn)行一層包裝。
下面實(shí)現(xiàn)一個(gè)最簡單的Promise
function Promise(fn) {
const callbacks = [];
this.then(calback) {
callbacks.push(callback)
}
function resolve(value) {
callbacks.forEach(callback => callback && callback(value))
}
fn(resolve)
return this
}
從代碼可以看到,then函數(shù)其實(shí)就是把傳入的函數(shù)放到一個(gè)回調(diào)數(shù)組里,這也讓原本單個(gè)的回調(diào)變成支持許多回調(diào)。resolve函數(shù)就是傳回我們將要執(zhí)行的函數(shù)中的回調(diào),他會(huì)調(diào)用每一個(gè)then里面添加的回調(diào)。最后,執(zhí)行傳入的函數(shù),并且返回this
可以向正常的Promise一樣使用:
var p = Promise(function process(resolve) {
setTimeout(() => {
console.log("timeout")
resolve()
}, 1000)
})
p
.then(() => console.log("timeout!!"))
寫到這里,也就明白resolve,reject是怎么實(shí)現(xiàn)的了。
await async
generator
await 和 async,需要在generator的基礎(chǔ)上來實(shí)現(xiàn)。generator可以實(shí)現(xiàn)控制函數(shù)間執(zhí)行順序的功能??赡苷f的有些抽象,但是理解了之后就感覺不難了。
function* generateValue() {
console.log("first")
yield "hello"
console.log("second")
return "world"
}
var g = generateValue()
console.log(g.next())
console.log(g.next())
// 輸出:
// first
// { value: 'hello', done: false }
// second
// { value: 'world', done: true }
這是一個(gè)生成器函數(shù),在其他語言也有類似的語法。在JavaScript中需要在函數(shù)名稱前帶上*才能使用yield來使用生成器。
generator函數(shù)的特點(diǎn)是,每次執(zhí)行到y(tǒng)ield時(shí),執(zhí)行權(quán)交到調(diào)用它的地方,等待下一次調(diào)用next()之后才會(huì)繼續(xù)執(zhí)行。
await async
那么generator與await和async是什么關(guān)系呢?
同樣的,我們先看問題
(axios是一個(gè)強(qiáng)大的HTTP client,可以同時(shí)用于web和node)
axios.get("https://www.github.com")
.then(result => console.log(result))
代碼中,如果我們想要正常的輸出結(jié)果,就可以在一個(gè)then的調(diào)用中來執(zhí)行l(wèi)og。但是怎么樣不使用Promise,而用更加自然的方式實(shí)現(xiàn)呢
function* syncDemo() {
const result = yield axios.get("https://www.github.com")
console.log('after get result', result)
}
我們利用yield,在上面的代碼中,如果執(zhí)行syncDemo(),函數(shù)執(zhí)行到y(tǒng)ield時(shí)會(huì)把代碼的控制權(quán)轉(zhuǎn)到調(diào)用函數(shù)的地方,于是參考generator的使用方法:
var g = syncDemo()
var p = g.next().value
p.then(result => g.next(result))
在代碼中g.next(result)中的result會(huì)傳回generator中賦給result,然后繼續(xù)執(zhí)行。
這就到了關(guān)鍵的地方,可以看到,利用generator的這個(gè)特性,我們已經(jīng)實(shí)現(xiàn)了一個(gè)可以順序執(zhí)行的異步函數(shù),但是在調(diào)用這個(gè)函數(shù)的時(shí)候,需要控制執(zhí)行的過程。那么是不是可以寫一個(gè)函數(shù)專門來自動(dòng)執(zhí)行這個(gè)步驟呢?
function runGenerator(fn) {
var g = fn()
g.next().value.then(result => {
g.next(result)
})
}
上面就是一個(gè)非常簡單的執(zhí)行函數(shù)。
調(diào)用:
runGenerator(syncDemo)
syncDemo就自動(dòng)執(zhí)行了。
而JavaScript的async,await做的也是同樣的事情。