解决函数回调经历了几个阶段, Promise 对象, Generator 函数到async函数。async函数目前是解决函数回调的最佳方案。不少语言目前都实现了async,包括Python ,java spring,go等。javascript
async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操做完成,再接着执行函数体内后面的语句。java
function getNum(num){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num+1)
}, 1000)
})
}
const func = async ()=>{
const f1 = await getNum(1)
const f2 = await getNum(f1)
console.log(f2)
// 输出3
}
func()
复制代码
async /await 须要在function外部书写async,在内部须要等待执行的函数前书写await便可spring
理解async函数须要先理解Generator函数,由于async函数是Generator函数的语法糖
。chrome
Generator是ES6标准引入的新的数据类型。Generator能够理解为一个状态机,内部封装了不少状态,同时返回一个迭代器Iterator对象。能够经过这个迭代器遍历相关的值及状态。 Generator的显著特色是能够屡次返回,每次的返回值做为迭代器的一部分保存下来,能够被咱们显式调用。promise
通常的函数使用function声明,return做为回调(没有遇到return,在结尾调用return undefined),只能够回调一次。而Generator函数使用function*定义,除了return,还使用yield返回屡次。浏览器
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}
const result = foo(0) // foo {<suspended>}
result.next(); // {value: 1, done: false}
result.next(); // {value: 2, done: false}
result.next(); // {value: 3, done: true}
result.next(); //{value: undefined, done: true}
复制代码
在chrome浏览器中这个例子里,咱们能够看到,在执行foo函数后返回了一个 Generator函数的实例。它具备状态值suspended和closed,suspended表明暂停,closed则为结束。可是这个状态是没法捕获的,咱们只能经过Generator函数的提供的方法获取当前的状态。 在执行next方法后,顺序执行了yield的返回值。返回值有value和done两个状态。value为返回值,能够是任意类型。done的状态为false和true,true即为执行完毕。在执行完毕后再次调用返回{value: undefined, done: true} 注意:在遇到return的时候,全部剩下的yield再也不执行,直接返回{ value: undefined, done: true }
异步
Generator函数提供了3个方法,next/return/throwasync
next方式是按步执行,每次返回一个值,同时也能够每次传入新的值做为计算函数
function* foo(x) {
let a = yield x + 1;
let b= yield a + 2;
return x + 3;
}
const result = foo(0) // foo {<suspended>}
result.next(1); // {value: 1, done: false}
result.next(2); // {value: 4, done: false}
result.next(3); // {value: 3, done: true}
result.next(4); //{value: undefined, done: true}
复制代码
return则直接跳过全部步骤,直接返回 {value: undefined, done: true}ui
throw则根据函数中书写try catch返回catch中的内容,若是没有写try,则直接抛出异常
function* foo(x) {
try{
yield x+1
yield x+2
yield x+3
yield x+4
}catch(e){
console.log('catch it')
}
}
const result = foo(0) // foo {<suspended>}
result.next(); // {value: 1, done: false}
result.next(); // {value: 2, done: false}
result.throw(); // catch it {value: undefined, done: true}
result.next(); //{value: undefined, done: true}
复制代码
这里能够看到在执行throw以前,顺序的执行了状态,可是在遇到throw的时候,则直接走进catche并改变了状态。
这里还要注意一下,由于状态机是根据执行状态的步骤而执行,因此若是执行thow的时候,没有遇到try catch则会直接抛错 如下面两个为例
function* foo(x) {
yield x+1
try{
yield x+2
}catch(e){
console.log('catch it')
}
}
const result = foo(0) // foo {<suspended>}
result.next(); // {value: 1, done: false}
result.next(); // {value: 2, done: false}
result.throw(); // catch it {value: undefined, done: true}
result.next(); //{value: undefined, done: true}
复制代码
这个例子与以前的执行状态同样,由于在执行到throw的时候,已经执行到try语句,因此能够执行,而下面的例子则不同
function* foo(x) {
yield x+1
try{
yield x+2
}catch(e){
console.log('catch it')
}
}
const result = foo(0) // foo {<suspended>}
result.next(); // {value: 1, done: false}
result.throw(); // Uncaught undefined
result.next(); //{value: undefined, done: true}
复制代码
执行throw的时候,尚未进入到try语句,因此直接抛错,抛出undefined为throw未传参数,若是传入参数则显示为传入的参数。此状态与未写try的抛错状态一致。
Generator函数的返回值是一个带有状态的Generator实例。它能够被for of 调用,进行遍历,且只可被for of 调用。此时将返回他的全部状态
function* foo(x) {
console.log('start')
yield x+1
console.log('state 1')
yield x+2
console.log('end')
}
const result = foo(0) // foo {<suspended>}
for(let i of result){
console.log(i)
}
//start
//1
//state 1
//2
//end
result.next() //{value: undefined, done: true}
复制代码
调用for of方法后,在后台调用next(),当done属性为true的时候,循环退出。所以Generator函数的实例将顺序执行一遍,再次调用时,状态为已完成
Generator函数中yield返回的值是能够被变量存储和改变的。
function* foo(x) {
let a = yield x + 0;
let b= yield a + 2;
yield x;
yield a
yield b
}
const result = foo(0)
result.next() // {value: 0, done: false}
result.next(2) // {value: 4, done: false}
result.next(3) // {value: 0, done: false}
result.next(4) // {value: 2, done: false}
result.next(5) // {value: 3, done: false}
复制代码
以上的执行结果中,咱们能够看到,在第二步的时候,咱们传入2这个参数,foo函数中的a的变量的值0被替换为2,而且在第4次迭代的时候,返回的是2。而第三次迭代的时候,传入的3参数,替换了b的值4,并在第5次迭代的时候返回了3。因此传入的参数,是替代上一次迭代的生成值。
在Generator函数中,咱们有时须要将多个迭代器的值合在一块儿,咱们可使用yield *的形式,将执行委托给另一个Generator函数
function* foo1() {
yield 1;
yield 2;
return "foo1 end";
}
function* foo2() {
yield 3;
yield 4;
}
function* foo() {
yield* foo1();
yield* foo2();
yield 5;
}
const result = foo();
console.log(iterator.next());// "{ value: 1, done: false }"
console.log(iterator.next());// "{ value: 2, done: false }"
console.log(iterator.next());// "{ value: 3, done: false }"
console.log(iterator.next());// "{ value: 4, done: false }"
console.log(iterator.next());// "{ value: 5, done: false }"
console.log(iterator.next());// "{ value: undefined, done: true }"
复制代码
foo在执行的时候,首先委托给了foo1,等foo1执行完毕,再委托给foo2。可是咱们发现,”foo1 end” 这一句并无输出。 在整个Generator中,return只能有一次,在委托的时候,全部的yield*都是以函数表达式的形式出现。return的值是表达式的结果,在委托结束以前其内部都是暂停的,等待到表达式的结果的时候,将结果直接返回给foo。此时foo内部没有接收的变量,因此未打印。 若是咱们但愿捕获这个值,可使用yield *foo()的方式进行获取。
如上,咱们掌握了Generator函数的使用方法。async/await语法糖就是使用Generator函数+自动执行器来运做的。 咱们能够参考如下例子
// 定义了一个promise,用来模拟异步请求,做用是传入参数++
function getNum(num){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num+1)
}, 1000)
})
}
//自动执行器,若是一个Generator函数没有执行完,则递归调用
function asyncFun(func){
var gen = func();
function next(data){
var result = gen.next(data);
if (result.done) return result.value;
result.value.then(function(data){
next(data);
});
}
next();
}
// 所须要执行的Generator函数,内部的数据在执行完成一步的promise以后,再调用下一步
var func = function* (){
var f1 = yield getNum(1);
var f2 = yield getNum(f1);
console.log(f2) ;
};
asyncFun(func);
复制代码
在执行的过程当中,判断一个函数的promise是否完成,若是已经完成,将结果传入下一个函数,继续重复此步骤。
async/await很是好理解,基本理解了Generator函数以后,几句话就能够描述清楚。这里没有过多的继续阐述Generator函数的内部执行逻辑及原理,若是有对此有深刻理解的童鞋,欢迎补充说明。