生成器時一種特殊類型的函數javascript
當從頭至尾運行標準函數時,最多隻生成一個值。html
而生成器函數會在幾回運行請求中暫停,所以每次運行均可能生成一個值java
//普通獲取JSON數據 異步 太耗時了
try{
var ninjas = syncGetJSON("ninjas.json");
var missions = syncGetJSON(ninjas[0].missionsUrl);
var missionDetails = syncGetJSON(missions[0].detailsUrl);
}catch(e){}
//回調解決 嵌套地獄
getJSON("ninjas.json",function(err,ninjas){
if(err){
//...
}
getJSON(ninjas[0].missionsUrl,function(err,missions){
if(err){
//...
}
getJSON(missions[0].detailsUrl,function(err,missionDetails){
if(err){
//...
}
//Study the intel plan
})
})
})
//生成器
//在 function 關鍵字後增長一個 *號 能夠定義生成器函數.在生成器函數中可以使用新的 yield 關鍵字
async(function* (){
try{
const ninjas = yield getJSON('ninjas.json');
const missions = yield getJSON(ninjas[0].missionsUrl);
const missionDescription = yield getJSON(missions[0].detailsUrl);
//Study the mission details
}catch(e){
//....
}
})
複製代碼
生成器函數是一個全新的函數類型,能生成一組值的序列,但每一個值的生成是基於每次請求,而且不一樣於標準函數的當即生成。咱們必須顯式的向生成器請求一個新的值,隨後生成器要麼相應一個新生成的值,要麼不會再生成新值node
生成器幾乎從不掛起,當對另外一個值的請求到來後,生成器就會從上次離開的位置恢復執行。json
function* WeaponGenerator(){
yield "Katana";
yield "Wakizashi";
yield "Kusarigama";
}
for(let weapon of WeaponGenerator()){
assert(weapon !=== undefined,weapon); //分三次輸出
}
複製代碼
調用生成器不會執行生成器函數,相反,它會建立一個叫迭代器的對象(iterator)。數組
調用生成器函數 不必定會執行 生成器函數體.會建立一個迭代器。經過建立迭代器對象,能夠與生成器通訊promise
function* WeaponGenerator(){
yield "Katana";
yield "Wakizashi";
}
const weaponsIterator = WeaponGenerator(); //建立一個迭代器,來控制生成器的執行
const result1 = weaponsIterator.next();
result1 //結果爲一個對象
result1.value; //"Katana" 包含一個返回值
result1.done; //還包含一個指示器 告訴咱們生成器是否還會生成值
//...
const result3 = weaponsIterator.next();
result3.value; //"undefined"
result3.done; //true 已完成
複製代碼
迭代器用於控制生成器的執行。迭代器對象暴露的最基本接口是 next 方法,這個方法能夠用來向生成器請求一個值,從而控制生成器:服務器
const result1 = weaponsIterator.next();
app
next 函數調用後,生成器就開始執行代碼,當代碼執行到 yield 關鍵字時,就會生成一箇中間結果(生成值序列中的一項),而後返回一個新對象,其中封裝告終果值和一個指示完成的指示器。異步
每當生成一個當前值後,生成器就會非阻塞的掛起執行,隨後耐心等待下一次值請求的到達。這是普通函數徹底不具備的強大特性。
//while循環迭代生成器
function* WeaponGenerator(){
yield "Katana";
yield "Wakizashi";
}
const weaponsIterator = WeaponGenerator(); //迭代器
let item;
while(!(item = weaponsIterator.next()).done){
item.value; //值
}
//for-of循環是對迭代器進行迭代的語法糖
for(var item of WeaponGenerator()){
item; //值
}
複製代碼
能夠在標準函數中調用另外一個標準函數 => 能夠把生成器的執行委託給另外一個生成器
//使用 yield 操做符將執行權交給另外一個生成器
//在迭代器上使用 yield* 操做符,程序會跳轉到另一個生成器上執行
function* WarriorGenerator(){
yield "Sun Tzu";
yield* NinjaGenerator(); //yield* 將執行權交給了另外一個生成器
yield "Genghis Khan";
}
function* NinjaGenerator(){
yield "Hattori";
yield "Yoshi";
}
//for-of 循環不會關心 WarriorGenerator 委託到另外一個生成器上,只關心 done 狀態到來以前都一直調用 next 方法
for(let warrior of WarriorGenerator()){
warrior; //都有
}
複製代碼
//使用生成器生成惟一ID序列
function* IdGenerator(){
let id = 0; //一個始終記錄ID的變量,這個變量沒法在生成器外部改變
while(true){ //循環生成無限長度的ID序列
yield ++id;
}
}
const idIterator = IdGenerator();
idIterator.next().value; //1
idIterator.next().value; //2
複製代碼
<div id="subTree">
<form>
<input type="text" />
</form>
<p>Paragraph</p>
<span>Span</span>
</div>
複製代碼
//遞歸函數
function traverseDOM(element,callback){
callback(element);
element = element.firstElementChild;
while(element){
traverseDOM(element,callback);
element = element.nextElementSibling;
}
}
const subTree = document.getElementById("subTree");
traverseDOM(subTree,function(element){
assert(element !== null,element.nodeName);
})
複製代碼
//用生成器遍歷 DOM 樹
function* DomTraversal(element){
yield element;
element = element.firstElementChild;
while(element){
yield* DomTraversal(element); //用 yield* 將迭代控制轉移到另外一個DomTraversal生成器實例上
element = element.nextElementSibing;
}
}
const subTree = document.getElementById('subTree');
for(let element of DomTraversal(subTree)){
assert(element !== null,element.nodeName);
}
複製代碼
告訴咱們沒必要使用回調函數的狀況下,使用生成器函數來解耦代碼,從而將生產值(HTML節點)的代碼和消費值(for-of循環打印、訪問過的節點)的代碼分隔開。迭代器比遞歸天然,保持一個開放的思路很重要。
向生成器發送值的最簡單方法是:調用函數並傳入實參
//向生成器發送數據及從生成器接收數據
//生成器能夠像其餘函數同樣接收 標準參數
function* NinjaGenerator(action){
const imposter = yield ("Hattori " + action);
//傳回的值將做爲yield表達式的返回值,所以impostrer的值是Hanzo
assert(imposter === "Hanzo","The generator has been infiltrated")
yield ("Yoshi (" + imposter + ") " + action)
}
const ninjaIterator = NinjaGenerator("skulk");
const result1 = ninjaIterator.next();
assert(result1.value === "Hattori skulk","Hattori is skulking");
const result2 = ninjaIterator.next("Hanzo");
assert(result2.value === "Yoshi(Hanzo)skulk","We have an imposter!")
複製代碼
除了第一次調用生成器的時候向生成器提供數據,咱們還能經過 next 方法向生成器傳入參數。這個過程當中,咱們把生成器函數從掛起狀態恢復到了執行狀態。
生成器把這個傳入的值用於整個yield表達式(生成器當前掛起的表達式)的值。
next() 方法爲等待的 yield 表達式提供了值,因此,若是沒有等待中的 yield 表達式,也就沒有什麼值能應用的。
基於此,咱們沒法經過第一次調用 next 方法向生成器提供該值。可是,若是你須要爲生成器提供一個初始值,你能夠調用生成器自身,就像 NinjaGenerator("skulk")
function* Gen(val){
val = yield val * 2;
yield val;
}
let generator = Gen(2);
let a1 = generator.next(3).value; //4
let a2 = generator.next(5).value; //5
複製代碼
每一個迭代器除了一個 next 方法,還有一個 throw 方法
//向生成器拋出異常
function* NinjaGenerator(){
try{
yield "Hattori";
fail("The expected exception didn't occur");
}catch(e){
assert(e === "Catch this!","Aha! We caught an exception");
}
}
const ninjaIterator = NinjaGenerator();
const result1 = ninjaIterator.next();
assert(result1.value === "Hattori","We got Hattori");
ninjaIterator.throw("Catch this!"); //向生成器拋出一個異常
//能夠用來改善異步服務器通訊
複製代碼
調用一個生成器不會實際執行它。它會建立一個新的 迭代器 ,經過該迭代器咱們才能從生成器中請求值。在生成器生成(讓渡)了一個值後,生成器會掛起執行並等待下一個請求的到來。
生成器時如何跟隨執行環境上下的呢?看下圖:
當咱們從生成器中取得控制權後,生成器的執行環境上下文一直是保存的,而不是像標準函數同樣退出後銷燬。
promise對象是對咱們如今還沒有獲得但未來會獲得值的佔位符
const ninjaPromise = new Promise((resolve,reject) => { //傳入兩個函數參數
resolve("Hattori");
});
ninjaPromise.then(ninja => {
assert(ninja === "Hattori","We were promised Hattori!");
},err => {
fail("There shouldn't be an error");
})
複製代碼
用新的內置構造函數 Promise 建立一個 promise 須要傳入一個函數,這個函數被稱爲 執行函數 ,它包含兩個參數 resolve 和 reject。當把這兩個內置函數:resolve 和 reject 做爲參數傳入 Promise 構造函數後,執行函數會馬上調用。
代碼調用 Promise 對象內置的 then 方法,咱們向這個方法中傳入兩個回調函數:一個成功回調函數和一個失敗回調函數。當承諾成功兌現(在 promise 上調用 resolve),前一個回調就會被調用,而當出現錯誤就會調用後一個回調函數(能夠是發生了一個未處理的異常,也能夠是在 promise 上調用了 reject)
回調函數的三個問題:
promise對象用於做爲 異步任務結果的佔位符。它表明了 一個咱們暫時還沒得到但在將來有望得到的值。
在一個 promise 對象的整個生命週期中,它會經歷多種狀態,如圖 6.10 所示。一個 promise 對象從等待(pending)狀態開始,此時咱們對承諾的值一無所知。所以一個等待狀態的 promise 對象也稱爲未實現(unresolved)的 promise。在程序執行的過程當中,若是 promise 的 resolve 函數被調用,promise 就會進入完成(fulfilled)狀態,在該狀態下咱們可以成功獲取到承諾的值
若是 promise 的 reject 函數被調用,或者若是一個未處理的異常在 promise 調用的過程當中發生了,promise 就會進入到拒絕狀態,儘管在該狀態下咱們沒法獲取承諾的值,但咱們至少知道了緣由。一旦某個 promise 進入到完成態或者拒絕態,它的狀態都不能再切換了(一個 promise 對象沒法從完成態再進入拒絕態或者相反)。
//promise的執行順序
report('At code start');
var ninjaDelayedPromise = new Promise((resolve, reject) => {
report('ninjaDelayedPromise executor');
setTimeout(() => {
report('Resolving ninjaDelayedPromise');
resolve('Hattori');
}, 500);
});
console.log(ninjaDelayedPromise);
assert(ninjaDelayedPromise !== null, 'After creating ninjaDelayedPromise');
ninjaDelayedPromise.then(ninja => {
assert(
ninja === 'Hattori',
'ninjaDelayedPromise resolve handled with Hattori'
);
});
const ninjaImmediatePromise = new Promise((resolve, reject) => {
report('ninjaImmediatePromise executor.Immediate resolve.');
resolve('Yoshi');
});
ninjaImmediatePromise.then(ninja => {
assert(ninja === 'Yoshi', 'ninjaImmediatePromise resolve handled with Yoshi');
});
report('At code end');
//結果以下
At code start
ninjaDelayedPromise executor
Promise { <pending> }
After creating ninjaDelayedPromise
ninjaImmediatePromise executor.Immediate resolve.
At code end
ninjaImmediatePromise resolve handled with Yoshi
Resolving ninjaDelayedPromise
ninjaDelayedPromise resolve handled with Hattori
複製代碼
Promise 是設計用來處理異步任務的。JavaScript 經過本次事件循環中的全部代碼都執行完畢後,調用 then 回調函數來處理 promise
//1、顯示拒絕
const promise = new Promise((resolve,reject) => {
reject("Explicitly reject a promise");
})
//1.若是promise被拒絕,第二個回調函數error老是被調用
promise.then(
() => fail("Happy path,won't be called!"),
error => pass("A promise was explicitly rejected!")
)
//2.用catch處理拒絕
promise.then(
() => fail("Happy path,won't be called!")
).catch(() => pass("Promise was also rejected"));
複製代碼
//2、隱式拒絕
const promise = new Promise((resolve,reject) => {
undeclaredVariable++; //未定義 拋出錯誤
});
promise.then(()=>fail("Happy path,won't be called!"))
.catch(error => pass("Third promise was alse rejected"));
複製代碼
function getJSON(url){
return new Promise((resolve,reject) => {
const request = new XMLHttpRequest();
request.open("GET",url);
request.onload = function(){
try{
if(this.status === 200){
resolve(JSON.parse(this.response)); //無效的JSON代碼
}else{ //服務器返回錯誤
reject(this.status + " " + this.statusText);
}
}catch(e){
reject(e.message);
}
}
//通訊中發生錯誤
request.onerror = function(){
reject(this.status + " " + this.statusText)
}
request.send();
})
}
//3個潛在的錯誤源:客戶端和服務器之間的鏈接錯誤、服務器返回錯誤的數據(無效的響應狀態碼)、無效的JSON代碼
getJSON("data/ninjas.json").then(ninjas => {
assert(ninjas !== null,"Ninjas obtained!");
}).catch(e => fail("Shouldn't be here:" + e));
複製代碼
咱們能夠在 then 函數上註冊一個回調函數,一旦 promise 成功兌現就觸發該回調函數
調用 then 方法後還能夠再返回一個新的 promise 對象
//鏈式調用 promise
getJSON('data/ninjas.json')
.then(ninjas => getJSON(ninjas[0].missionsUrl))
.then(missions => getJSON(missions[0].detailsUrl))
.then(mission => assert(mission !== null,'Ninja mission obtained!'))
.catch(error => fail('An error has occurred'))
複製代碼
...catch(error => fail("An error has occurred:" + err));
複製代碼
若是錯誤在前邊的任何一個 promise 中產生,catch 方法都會捕捉到,統一處理。
Promise.all([
getJSON("data/ninjas.json"),
getJSON("data/mapInfo.json"),
getJSON("data/plan.json")
]).then(results => {
const ninjas = results[0],mapInfo = results[1],plan = results[2];
//...
}).catch(error => {
fail("A problem in carrying out our plan!");
})
複製代碼
經過內置方法 Promise.all
能夠等待多個 promise。這個方法將一個 promise 數組做爲參數,而後建立一個新的 promise 對象,一旦數組中的 promise 所有被解決,這個返回的 promise 就會被解決,一旦其中一個 promise 失敗了,那麼整個新 promise 對象也會被拒絕。
後續的回調函數接收成功值組成的數組,數組中的每一項都對應 promise 數組中的對應項。
Promise.race([
getJSON("data/yoshi.json"),
getJSON("data/hattori.json"),
getJSON("data/hanzo.json")
]).then(ninja => {
//...
}).catch(error => fail("Failure!"));
複製代碼
使用 Promise.race 方法傳入一個 promise 數組會返回一個全新的 promise 對象,一旦數組中某一個 promise 被處理或被拒絕,這個返回的 promise 就一樣會被處理或被拒絕。
如下分別以 同步 和 異步 的代碼來書寫
try {
const ninjas = syncGetJSON('data/ninjas.json');
const missions = syncGetJSON('ninjas[0].missionsUrl');
const missionDetails = syncGetJSON(missions[0].detailsUrl);
} catch (e) {
//
}
複製代碼
缺點是: UI 被阻塞了
解決方案:將 生成器 和 promise 相結合
從生成器中讓渡後會掛起執行而不會發生阻塞.僅需調用生成器迭代器的next方法,就能夠喚醒生成器並繼續執行.而promise在將來觸發某種條件的狀況下獲得允諾的值,發生錯誤時執行相應的回調函數.
//將 promise 和 生成器結合
function async(generator) {
var iterator = generator();
function handle(iteratorResult) {
if (iteratorResult.done) {
return;
}
const iteratorValue = iteratorResult.value;
if (iteratorValue instanceof Promise) {
iteratorValue
.then(res => handle(iterator.next(res)))
.catch(err => iterator.throw(err));
}
}
try {
handle(iterator.next());
} catch (error) {
iterator.throw(error);
}
}
async(function*() {
try {
const ninjas = yield getJSON('data/ninjas.json');
const missions = yield getJSON(ninjas[0].missionsUrl);
const missionDescription = yield getJSON(missions[0].detailUrl);
} catch (error) {}
});
複製代碼
//用異步的方式寫同步代碼
//以前代碼
getJSON('data/ninjas.json',(err,ninjas) => {
if(err){...}
getJSON(ninjas[0].missionsUrl,(err,missions) => {
if(err){...}
console.log(missions);
})
})
//async
async(function*(){
try{
const ninjas = yield getJSON('data/ninjas.json');
const missions = yield getJSON(ninjas[0].missionsUrl);
}catch(e){
//error
}
})
複製代碼
在關鍵字 function 以前使用關鍵字 async,代表當前的函數依賴一個異步返回的值。在每一個異步任務的位置上,都要放置一個 await 關鍵字,用來告訴 JavaScript 引擎,請在不阻塞應用執行的狀況下在這個位置上等待執行結果
(async function() {
try {
const ninjas = await getJSON('data/ninjas.json');
const missions = await getJSON(ninjas[0].missionsUrl);
console.log(missions);
} catch (error) {
console.log('Error:', error);
}
})();
複製代碼
同步代碼讓咱們更容易理解、使用標準控制流以及異常處理機制、try-catch語句的能力。
異步代碼有天生的非阻塞,當等待長時間運行的異步任務時,應用的執行不該該被阻塞。
經過將生成器和promise相結合咱們可以使用 同步代碼 來簡化 異步任務