引言
接触过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
来处理异步。