JavaScript原有的四種表示'集合'的數據結構,Object、Array、Set、Map。shell
遍歷器(Iterator)是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署了Iterator接口,就能夠完成遍歷操做。編程
Iterator 的做用有三個:promise
遍歷器提供了一個指針,指向當前對象的某個屬性,使用next方法,就能夠將指針移動到下一個屬性。next方法返回一個包含value和done兩個屬性的對象。其中,value屬性是當前遍歷位置的值,done屬性是一個布爾值,表示遍歷是否結束。
原生具有 Iterator 接口的數據結構以下:數據結構
對於原生部署 Iterator 接口的數據結構,不用本身寫遍歷器生成函數,for...of循環會自動遍歷它們。其餘數據結構(主要是對象)的 Iterator 接口,都須要本身在Symbol.iterator屬性上面部署,這樣纔會被for...of循環遍歷。併發
class RangeIterator { constructor(start, stop){ this.value = start; this.stop = stop; } [Symbol.iterator]{return this;} next(){ let value = this.value; if(value < this.stop){ this.value++; return {done : false, value : value}; } return {done : true, value : undefined}; } } function range(start, stop){ return new RangeIterator(start, stop); } for(let v of range(0,3)) { console.log(value); // 0 1 2 }
Generator 函數是 ES6 提供的一種異步編程解決方案。異步
因爲 Generator 函數返回的遍歷器對象,只有調用next方法纔會遍歷下一個內部狀態,因此其實提供了一種能夠暫停執行的函數。yield表達式就是暫停標誌。async
遍歷器對象的next方法的運行邏輯以下。異步編程
(1)遇到yield表達式,就暫停執行後面的操做,並將緊跟在yield後面的那個表達式的值,做爲返回的對象的value屬性值。函數
(2)下一次調用next方法時,再繼續往下執行,直到遇到下一個yield表達式。post
(3)若是沒有再遇到新的yield表達式,就一直運行到函數結束,直到return語句爲止,並將return語句後面的表達式的值,做爲返回的對象的value屬性值。
(4)若是該函數沒有return語句,則返回的對象的value屬性值爲undefined。
須要注意的是,yield表達式後面的表達式,只有當調用next方法、內部指針指向該語句時纔會執行,所以等於爲 JavaScript 提供了手動的「惰性求值」(Lazy Evaluation)的語法功能。
Generator 函數能夠不用yield表達式,這時就變成了一個單純的暫緩執行函數。
yield表達式只能用在 Generator 函數裏面,用在其餘地方都會報錯。
yield表達式若是用在另外一個表達式之中,必須放在圓括號裏面。
function* demo() { console.log('Hello' + yield); // SyntaxError console.log('Hello' + yield 123); // SyntaxError console.log('Hello' + (yield)); // OK console.log('Hello' + (yield 123)); // OK }
yield表達式用做函數參數或放在賦值表達式的右邊,能夠不加括號。
function* demo() { foo(yield 'a', yield 'b'); // OK let input = yield; // OK }
yield表達式自己沒有返回值,或者說老是返回undefined。next方法能夠帶一個參數,該參數就會被看成上一個yield表達式的返回值。
因爲 Generator 函數就是遍歷器生成函數,所以能夠把 Generator 賦值給對象的Symbol.iterator屬性,從而使得該對象具備 Iterator 接口。
var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...myIterable] // [1, 2, 3]
Generator 函數返回的遍歷器對象,還有一個return方法,能夠返回給定的值,而且終結遍歷 Generator 函數。
Generator 函數返回的遍歷器對象,都有一個throw方法,能夠在函數體外拋出錯誤,而後在 Generator 函數體內捕獲。
若是在 Generator 函數內部,調用另外一個 Generator 函數,默認狀況下是沒有效果的。
這個就須要用到yield*表達式,用來在一個 Generator 函數裏面執行另外一個 Generator 函數。
function* bar() { yield 'x'; yield* foo(); yield 'y'; } // 等同於 function* bar() { yield 'x'; yield 'a'; yield 'b'; yield 'y'; } // 等同於 function* bar() { yield 'x'; for (let v of foo()) { yield v; } yield 'y'; } for (let v of bar()){ console.log(v); } // "x" // "a" // "b" // "y"
若是一個對象的屬性是 Generator 函數,能夠簡寫成下面的形式。
let obj = {
}
};
Generator 函數老是返回一個遍歷器,ES6 規定這個遍歷器是 Generator 函數的實例,也繼承了 Generator 函數的prototype對象上的方法。
function* g() {} g.prototype.hello = function () { return 'hi!'; }; let obj = g(); obj instanceof g // true obj.hello() // 'hi!'
上面代碼代表,Generator 函數g返回的遍歷器obj,是g的實例,並且繼承了g.prototype。可是,若是把g看成普通的構造函數,並不會生效,由於g返回的老是遍歷器對象,而不是this對象。
function* g() { this.a = 11; } let obj = g(); obj.next(); obj.a // undefined
上面代碼中,Generator 函數g在this對象上面添加了一個屬性a,可是obj對象拿不到這個屬性。
async 函數是 Generator 函數的語法糖。
async函數就是將 Generator 函數的星號(*)替換成async,將yield替換成await,僅此而已。
const fs = require('fs'); const readFile = function (fileName) { return new Promise(function (resolve, reject) { fs.readFile(fileName, function(error, data) { if (error) return reject(error); resolve(data); }); }); }; const gen = function* () { const f1 = yield readFile('/etc/fstab'); const f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };
寫成async函數,就是下面這樣。
const asyncReadFile = async function () { const f1 = await readFile('/etc/fstab'); const f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };
async函數對 Generator 函數的改進,體如今如下四點。
正常狀況下,await命令後面是一個 Promise 對象。若是不是,就返回對應的值。
一、await命令後面的Promise對象,運行結果多是rejected,因此最好把await命令放在try...catch代碼塊中。
async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } } // 另外一種寫法 async function myFunction() { await somethingThatReturnsAPromise() .catch(function (err) { console.log(err); }); }
二、 多個await命令後面的異步操做,若是不存在繼發關係,最好讓它們同時觸發。
// 寫法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 寫法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise;
上面兩種寫法,getFoo和getBar都是同時觸發,這樣就會縮短程序的執行時間。
三、await命令只能用在async函數之中,若是用在普通函數,就會報錯。
async function dbFuc(db) { let docs = [{}, {}, {}]; // 報錯 docs.forEach(function (doc) { await db.post(doc); }); }
四、若是確實但願多個請求併發執行,可使用Promise.all方法。當三個請求都會resolved時,下面兩種寫法效果相同。
async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = await Promise.all(promises); console.log(results); } // 或者使用下面的寫法 async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = []; for (let promise of promises) { results.push(await promise); } console.log(results); }
async:
await: