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: