我們處理異步的方式,從開始的回調,到Promise,再到現在的async,await,變得越來越方便,直觀了。但是知其然要知其所以然,所以我們一步步來分析他們是如何實現的(需要知道Promise的使用方法,await async的使用方法,以及generator的功能)。
Promise
function process() {
setTimeout(() => {
console.log("timeout")
}, 1000)
}
如果我們想要執行上面的函數,并且在計時結束后的回調中執行某些動作,應該怎么辦呢?
我們可以使用一個回調:
function process(callback) {
setTimeout(() => {
console.log("timeout")
callback()
}, 1000)
}
process(function doSomething() {
console.log("do something")
})
這是我們正常的回調方式,但是這樣的方式不夠靈活,而且當需要嵌套回調的時候,往往就寫成了callback hell。Promise其實就是對上面的process進行一層包裝。
下面實現一個最簡單的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
函數其實就是把傳入的函數放到一個回調數組里,這也讓原本單個的回調變成支持許多回調。resolve
函數就是傳回我們將要執行的函數中的回調,他會調用每一個then里面添加的回調。最后,執行傳入的函數,并且返回this
可以向正常的Promise一樣使用:
var p = Promise(function process(resolve) {
setTimeout(() => {
console.log("timeout")
resolve()
}, 1000)
})
p
.then(() => console.log("timeout!!"))
寫到這里,也就明白resolve,reject是怎么實現的了。
await async
generator
await 和 async,需要在generator的基礎上來實現。generator可以實現控制函數間執行順序的功能。可能說的有些抽象,但是理解了之后就感覺不難了。
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 }
這是一個生成器函數,在其他語言也有類似的語法。在JavaScript中需要在函數名稱前帶上*
才能使用yield
來使用生成器。
generator函數的特點是,每次執行到yield時,執行權交到調用它的地方,等待下一次調用next()之后才會繼續執行。
await async
那么generator與await和async是什么關系呢?
同樣的,我們先看問題
(axios是一個強大的HTTP client,可以同時用于web和node)
axios.get("https://www.github.com")
.then(result => console.log(result))
代碼中,如果我們想要正常的輸出結果,就可以在一個then的調用中來執行log。但是怎么樣不使用Promise,而用更加自然的方式實現呢
function* syncDemo() {
const result = yield axios.get("https://www.github.com")
console.log('after get result', result)
}
我們利用yield,在上面的代碼中,如果執行syncDemo(),函數執行到yield時會把代碼的控制權轉到調用函數的地方,于是參考generator的使用方法:
var g = syncDemo()
var p = g.next().value
p.then(result => g.next(result))
在代碼中g.next(result)
中的result會傳回generator中賦給result,然后繼續執行。
這就到了關鍵的地方,可以看到,利用generator的這個特性,我們已經實現了一個可以順序執行的異步函數,但是在調用這個函數的時候,需要控制執行的過程。那么是不是可以寫一個函數專門來自動執行這個步驟呢?
function runGenerator(fn) {
var g = fn()
g.next().value.then(result => {
g.next(result)
})
}
上面就是一個非常簡單的執行函數。
調用:
runGenerator(syncDemo)
syncDemo就自動執行了。
而JavaScript的async,await做的也是同樣的事情。