解決函數回調經歷了幾個階段, Promise 對象, Generator 函數到async函數。async函數目前是解決函數回調的最佳方案。不少語言目前都實現了async,包括Python ,java spring,go等。javascript
async 函數返回一個 Promise 對象,當函數執行的時候,一旦遇到 await 就會先返回,等到觸發的異步操做完成,再接着執行函數體內後面的語句。java
function getNum(num){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num+1)
}, 1000)
})
}
const func = async ()=>{
const f1 = await getNum(1)
const f2 = await getNum(f1)
console.log(f2)
// 輸出3
}
func()
複製代碼
async /await 須要在function外部書寫async,在內部須要等待執行的函數前書寫await便可spring
理解async函數須要先理解Generator函數,由於async函數是Generator函數的語法糖
。chrome
Generator是ES6標準引入的新的數據類型。Generator能夠理解爲一個狀態機,內部封裝了不少狀態,同時返回一個迭代器Iterator對象。能夠經過這個迭代器遍歷相關的值及狀態。 Generator的顯著特色是能夠屢次返回,每次的返回值做爲迭代器的一部分保存下來,能夠被咱們顯式調用。promise
通常的函數使用function聲明,return做爲回調(沒有遇到return,在結尾調用return undefined),只能夠回調一次。而Generator函數使用function*定義,除了return,還使用yield返回屢次。瀏覽器
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}
const result = foo(0) // foo {<suspended>}
result.next(); // {value: 1, done: false}
result.next(); // {value: 2, done: false}
result.next(); // {value: 3, done: true}
result.next(); //{value: undefined, done: true}
複製代碼
在chrome瀏覽器中這個例子裏,咱們能夠看到,在執行foo函數後返回了一個 Generator函數的實例。它具備狀態值suspended和closed,suspended表明暫停,closed則爲結束。可是這個狀態是沒法捕獲的,咱們只能經過Generator函數的提供的方法獲取當前的狀態。 在執行next方法後,順序執行了yield的返回值。返回值有value和done兩個狀態。value爲返回值,能夠是任意類型。done的狀態爲false和true,true即爲執行完畢。在執行完畢後再次調用返回{value: undefined, done: true} 注意:在遇到return的時候,全部剩下的yield再也不執行,直接返回{ value: undefined, done: true }
異步
Generator函數提供了3個方法,next/return/throwasync
next方式是按步執行,每次返回一個值,同時也能夠每次傳入新的值做爲計算函數
function* foo(x) {
let a = yield x + 1;
let b= yield a + 2;
return x + 3;
}
const result = foo(0) // foo {<suspended>}
result.next(1); // {value: 1, done: false}
result.next(2); // {value: 4, done: false}
result.next(3); // {value: 3, done: true}
result.next(4); //{value: undefined, done: true}
複製代碼
return則直接跳過全部步驟,直接返回 {value: undefined, done: true}ui
throw則根據函數中書寫try catch返回catch中的內容,若是沒有寫try,則直接拋出異常
function* foo(x) {
try{
yield x+1
yield x+2
yield x+3
yield x+4
}catch(e){
console.log('catch it')
}
}
const result = foo(0) // foo {<suspended>}
result.next(); // {value: 1, done: false}
result.next(); // {value: 2, done: false}
result.throw(); // catch it {value: undefined, done: true}
result.next(); //{value: undefined, done: true}
複製代碼
這裏能夠看到在執行throw以前,順序的執行了狀態,可是在遇到throw的時候,則直接走進catche並改變了狀態。
這裏還要注意一下,由於狀態機是根據執行狀態的步驟而執行,因此若是執行thow的時候,沒有遇到try catch則會直接拋錯 如下面兩個爲例
function* foo(x) {
yield x+1
try{
yield x+2
}catch(e){
console.log('catch it')
}
}
const result = foo(0) // foo {<suspended>}
result.next(); // {value: 1, done: false}
result.next(); // {value: 2, done: false}
result.throw(); // catch it {value: undefined, done: true}
result.next(); //{value: undefined, done: true}
複製代碼
這個例子與以前的執行狀態同樣,由於在執行到throw的時候,已經執行到try語句,因此能夠執行,而下面的例子則不同
function* foo(x) {
yield x+1
try{
yield x+2
}catch(e){
console.log('catch it')
}
}
const result = foo(0) // foo {<suspended>}
result.next(); // {value: 1, done: false}
result.throw(); // Uncaught undefined
result.next(); //{value: undefined, done: true}
複製代碼
執行throw的時候,尚未進入到try語句,因此直接拋錯,拋出undefined爲throw未傳參數,若是傳入參數則顯示爲傳入的參數。此狀態與未寫try的拋錯狀態一致。
Generator函數的返回值是一個帶有狀態的Generator實例。它能夠被for of 調用,進行遍歷,且只可被for of 調用。此時將返回他的全部狀態
function* foo(x) {
console.log('start')
yield x+1
console.log('state 1')
yield x+2
console.log('end')
}
const result = foo(0) // foo {<suspended>}
for(let i of result){
console.log(i)
}
//start
//1
//state 1
//2
//end
result.next() //{value: undefined, done: true}
複製代碼
調用for of方法後,在後臺調用next(),當done屬性爲true的時候,循環退出。所以Generator函數的實例將順序執行一遍,再次調用時,狀態爲已完成
Generator函數中yield返回的值是能夠被變量存儲和改變的。
function* foo(x) {
let a = yield x + 0;
let b= yield a + 2;
yield x;
yield a
yield b
}
const result = foo(0)
result.next() // {value: 0, done: false}
result.next(2) // {value: 4, done: false}
result.next(3) // {value: 0, done: false}
result.next(4) // {value: 2, done: false}
result.next(5) // {value: 3, done: false}
複製代碼
以上的執行結果中,咱們能夠看到,在第二步的時候,咱們傳入2這個參數,foo函數中的a的變量的值0被替換爲2,而且在第4次迭代的時候,返回的是2。而第三次迭代的時候,傳入的3參數,替換了b的值4,並在第5次迭代的時候返回了3。因此傳入的參數,是替代上一次迭代的生成值。
在Generator函數中,咱們有時須要將多個迭代器的值合在一塊兒,咱們可使用yield *的形式,將執行委託給另一個Generator函數
function* foo1() {
yield 1;
yield 2;
return "foo1 end";
}
function* foo2() {
yield 3;
yield 4;
}
function* foo() {
yield* foo1();
yield* foo2();
yield 5;
}
const result = foo();
console.log(iterator.next());// "{ value: 1, done: false }"
console.log(iterator.next());// "{ value: 2, done: false }"
console.log(iterator.next());// "{ value: 3, done: false }"
console.log(iterator.next());// "{ value: 4, done: false }"
console.log(iterator.next());// "{ value: 5, done: false }"
console.log(iterator.next());// "{ value: undefined, done: true }"
複製代碼
foo在執行的時候,首先委託給了foo1,等foo1執行完畢,再委託給foo2。可是咱們發現,」foo1 end」 這一句並無輸出。 在整個Generator中,return只能有一次,在委託的時候,全部的yield*都是以函數表達式的形式出現。return的值是表達式的結果,在委託結束以前其內部都是暫停的,等待到表達式的結果的時候,將結果直接返回給foo。此時foo內部沒有接收的變量,因此未打印。 若是咱們但願捕獲這個值,可使用yield *foo()的方式進行獲取。
如上,咱們掌握了Generator函數的使用方法。async/await語法糖就是使用Generator函數+自動執行器來運做的。 咱們能夠參考如下例子
// 定義了一個promise,用來模擬異步請求,做用是傳入參數++
function getNum(num){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num+1)
}, 1000)
})
}
//自動執行器,若是一個Generator函數沒有執行完,則遞歸調用
function asyncFun(func){
var gen = func();
function next(data){
var result = gen.next(data);
if (result.done) return result.value;
result.value.then(function(data){
next(data);
});
}
next();
}
// 所須要執行的Generator函數,內部的數據在執行完成一步的promise以後,再調用下一步
var func = function* (){
var f1 = yield getNum(1);
var f2 = yield getNum(f1);
console.log(f2) ;
};
asyncFun(func);
複製代碼
在執行的過程當中,判斷一個函數的promise是否完成,若是已經完成,將結果傳入下一個函數,繼續重複此步驟。
async/await很是好理解,基本理解了Generator函數以後,幾句話就能夠描述清楚。這裏沒有過多的繼續闡述Generator函數的內部執行邏輯及原理,若是有對此有深刻理解的童鞋,歡迎補充說明。