引言
接觸過Ajax請求的會遇到過異步調用的問題,爲了保證調用順序的正確性,通常咱們會在回調函數中調用,也有用到一些新的解決方案如Promise
相關的技術。html
在異步編程中,還有一種經常使用的解決方案,它就是Generator
生成器函數。顧名思義,它是一個生成器,它也是一個狀態機,內部擁有值及相關的狀態,生成器返回一個迭代器Iterator
對象,咱們能夠經過這個迭代器,手動地遍歷相關的值、狀態,保證正確的執行順序。編程
Iterator接口
什麼是Iterator接口promise
遍歷器(Iterator
)就是這樣一種機制。它是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署Iterator
接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。數據結構
Iterator的做用
for...of
循環,Iterator
接口主要供for...of
消費。Iterator實現
function makeIterator(array) { var nextIndex = 0; return { next: function() { return nextIndex < array.length ? {value: array[nextIndex++], done: false} : {value: undefined, done: true}; } }; } var it = makeIterator(['a', 'b']); it.next() // { value: "a", done: false } it.next() // { value: "b", done: false } it.next() // { value: undefined, done: true }
原生具有Iterator接口的數據結構
查看一下Map
下面的所掛載的Iterator
。dom
let map = new Map(); console.log(map.__proto__);
輸出結果:異步
clear:ƒ clear() constructor:ƒ Map() delete:ƒ delete() entries:ƒ entries() forEach:ƒ forEach() get:ƒ () has:ƒ has() keys:ƒ keys() set:ƒ () size:(...) values:ƒ values() Symbol(Symbol.iterator):ƒ entries() Symbol(Symbol.toStringTag):"Map" get size:ƒ size() __proto__:Object
如何爲Object部署一個Iterator接口
function iteratorObject(obj){ let keys = Object.keys(obj); let index = -1; return { next(){ index++; return index<keys.length?{ value:obj[keys[index]], key:keys[index], done:false }:{ value:undefined, key:undefined, done:true } } } } let obj = {a:1,b:2,c:3}; let iter = iteratorObject(obj); console.log(iter.next()); // {value: 1, key: "a", done: false} console.log(iter.next()); // {value: 2, key: "b", done: false} console.log(iter.next()); // {value: 3, key: "c", done: false} console.log(iter.next()); // {value: undefined, key: undefined, done: true}
經過上面的方法能夠簡單的爲Object
部署了一個Iterator
接口。async
Generator函數
Generator是ES6的新特性,經過yield
關鍵字,可讓函數的執行流掛起,那麼便爲改變執行流程提供了可能。異步編程
Generator語法
dome:函數
function * greneratorDome(){ yield "Hello"; yield "World"; return "ending"; } let grenDome = greneratorDome(); console.log(grenDome)
上面的代碼中定義了一個Generator
函數,獲取到了函數返回的對象。下面是其輸出結果。prototype
原型鏈:
greneratorDome {<suspended>} __proto__:Generator __proto__:Generator constructor:GeneratorFunction {prototype: Generator, constructor: ƒ, Symbol(Symbol.toStringTag): "GeneratorFunction"} next:ƒ next() return:ƒ return() throw:ƒ throw() Symbol(Symbol.toStringTag):"Generator" __proto__:Object [[GeneratorStatus]]:"suspended" [[GeneratorFunction]]:ƒ* greneratorDome() [[GeneratorReceiver]]:Window [[GeneratorLocation]]:test.html:43 [[Scopes]]:Scopes[3]
經過上面的輸出結果能夠看的出來,沿着原型鏈向上查找就存在一個next
方法,這個方法與Iterator
接口返回的結果是大同小異的。
繼續延續dome代碼,並使用next
方法向下執行。
function * greneratorDome(){ yield "Hello"; yield "World"; return "Ending"; } let grenDome = greneratorDome(); console.log(grenDome.next()); // {value: "Hello", done: false} console.log(grenDome.next()); // {value: "World", done: false} console.log(grenDome.next()); // {value: "Ending", done: true} console.log(grenDome.next()); // {value: undefined, done: true}
在最開始的地方有提到過Generator
函數,最後返回的是一個Iterator
對象,這也就不難理解了。
異步的Generator
dome
function a (){ setTimeout(() => { alert("我是後彈出"); },1000) } function b (){ alsert("我是先彈出"); } function * grenDome (){ yield a(); yield b(); } let gren = grenDome(); gren.next(); gren.next(); // 輸出結果 // 我是先彈出 // 我是後彈出
結合Promise
function a (){ return new Promise((resolve,reject) => { setTimeOut(() => { console.log(1) resolve("a"); }) }) } function b (){ return new Promise((resolve,reject) => { console.log(2) resolve("b"); }) } function * grenDome (){ yield a(); yield b(); return new Promise((resolve,reject) => { resolve("grenDome內部") }) } let gren = grenDome(); // console.log(gren.next()) // {value: Promise, done: false} // console.log(gren.next()) // {value: Promise, done: false} // console.log(gren.next()) // {value: Promise, done: true} // console.log(gren.next()) // {value: undefined, done: true} gren.next().value.then((res) => { console.log(res); // a函數 }) gren.next().value.then((res) => { console.log(res); // b函數 }) gren.next().value.then((res) => { console.log(res); // grenDome內部 }) // 輸出結果 // a // b // grenDome內部
在上面的代碼中有一點是須要注意的,在grenDome
函數裏面最後return
出去了一個Promise
,可是在輸出的時候雖然done
屬性已經爲true
可是value
裏面仍然會存有一個promise
對象,實際上done
表示的是對應yield
關鍵字的函數已經遍歷完成了。
Async/Await
Async/await
是Javascript
編寫異步程序的新方法。以往的異步方法無外乎回調函數和Promise
。可是Async/await
創建於Promise
之上,換句話來講使用了Generator
函數作了語法糖。
async
函數就是隧道盡頭的亮光,不少人認爲它是異步操做的終極解決方案。
什麼是Async/Await
async
顧名思義是「異步」的意思,async
用於聲明一個函數是異步的。而await
從字面意思上是「等待」的意思,就是用於等待異步完成。而且await
只能在async
函數中使用。
Async/Await語法
function timeout(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); }; async function asyncPrint(value, ms) { await timeout(ms); console.log(value); }; asyncPrint('hello world',2000); // 在2000ms以後輸出`hello world`
返回Promse對象
一般async
、await
都是跟隨Promise
一塊兒使用的。爲何這麼說呢?由於async
返回的都是一個Promise
對象同時async
適用於任何類型的函數上。這樣await
獲得的就是一個Promise
對象,若是不是Promise
對象的話那async
返回的是什麼就是什麼。
async function f() { return 'hello world'; } f().then(v => console.log(v)); // hello world
async
函數返回一個Promise
對象,可使用then
方法添加回調函數。當函數執行的時候,一旦遇到await
就會先返回,等到異步操做完成,再接着執行函數體內後面的語句。
function a(){ return new Promise((resolve,reject) => { console.log("a函數") resolve("a函數") }) } function b (){ return new Promise((resolve,reject) => { console.log("b函數") resolve("b函數") }) } async function dome (){ let A = await a(); let B = await b(); return Promise.resolve([A,B]); } dome().then((res) => { console.log(res); });
執行機制
前面已經說過await
是等待的意思,以後等前面的代碼執行完成以後纔會繼續向下執行。
function a(){ return new Promise((resolve,reject) => { resolve("a"); console.log("a:不行") }) } function b (){ return new Promise((resolve,reject) => { resolve("b"); console.log("b:不行"); }) } async function dome (){ await a(); await b(); console.log("雖然我在後面,可是我想要先執行能夠麼?") } dome(); // 輸出結果 // a:不行 // b:不行 // 雖然我在後面,可是我想要先執行能夠麼?
另一個列子
function timeout1(ms) { return new Promise((resolve) => { setTimeout(() => { console.log("timeout1") resolve(); },ms); }); }; function timeout2(ms) { return new Promise((resolve) => { setTimeout(() => { console.log("timeout2"); resolve(); },ms); }); }; async function asyncPrint() { await timeout1(1000); await timeout2(2000); }; asyncPrint().then((res) => { console.log(res); }).catch((err) => { console.log(err) }) // 1s 後輸出timeout1 // 3s 後輸出timeout2 // undefined
async、await錯誤處理
JavaScript異步請求確定會有請求失敗的狀況,上面也說到了async返回的是一個Promise對象。既然是返回一個Promise對象的話那處理當異步請求發生錯誤的時候咱們就要處理reject的狀態了。
在Promise中當請求reject的時候咱們可使用catch。爲了保持代碼的健壯性使用async、await的時候咱們使用try catch來處理錯誤。
async function f() { await Promise.reject('出錯了'); await Promise.resolve('hello world'); } async function b() { try { await f(); } catch(err) { console.log(err); } } b(); // 出錯了
總結
Iterator接口
遍歷器對象除了具備next
方法,還能夠具備return
方法和throw
方法。若是你本身寫遍歷器對象生成函數,那麼next
方法是必須部署的,return
方法和throw
方法是否部署是可選的。
Es6
提供不少API
都是基於Iterator
接口,好比解構,for...of循環,拓展運算等。
Generator函數
調用Generator
函數,返回一個遍歷器對象,表明Generator
函數的內部指針。之後每次調用遍歷器對象的next
方法,就會返回一個有着value
和done
兩個屬性的對象。 value
屬性表示當前的內部狀態的值,是yield
語句後面那個表達式的值;done
屬性是一個布爾值,表示是否遍歷結束
Async/Await
Async/await
是近些年來JavaScript
最具革命性的新特性之一。他讓讀者意識到使用Promise
存在的一些問題,並提供了自身來代替Promise
的方案。他使得異步代碼變的再也不明顯,咱們好不容易已經學會並習慣了使用回調函數或者then
來處理異步。