当我们进行异步嵌套时,就会写出如下代码。这类代码会随着嵌套层数的增长,维护难度几何上升。
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
为了解决异步嵌套的问题,机智的人们推出了Promise方案,实质上也只是将嵌套碾平,并没有解决异步带来的难以捕获错误问题。
Q.fcall(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// Do something with value4
}, function (error) {
// Handle any error from step1 through step4
})
.done();
传统语言的错误处理
public static boolean catchTest() {
try {
// 远程获取数据
} catch (Exception e) {
System.out.println(" -- Exception --");
return catchMethod();
}
}
JS的错误处理
try{
$.get('http://0.0.0.0:8082')
}catch (error){
// 并不能捕获到异步的异常
console.error(error)
}
为了解决上述的异步问题,诞生了generator
Generator函数是ES6提供的一种异步编程解决方案。可以把它理解成,Generator函数是一个状态机,封装了多个内部状态。执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态。
形式上,Generator函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield语句,定义不同的内部状态
// *必须跟在function在后面,至于空格有没有有几个都不关心
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
function *gen(name){
var yieldName = yield name;
console.log('name:'+name);
console.log('yieldName:'+yieldName);
return yieldName;
}
var g = gen('lili');
// Object {value: "lili", done: false}
var first = g.next();
// Object {value: "sandy", done: true}
var second = g.next('sandy');
yield其实是一个暂停标志,当引擎执行到yield这句话时generator函数暂停执行,将执行权限交给调用g.next()的函数,yield 后面的值会作为{value,done}中的value一起返回给g.next();
{value: "lili", done: false}
var yieldName = 'sandy'
;后面没有yield函数,所以一直执行到return yildName;所以g.next('sandy')返回{value: "sandy", done: true}
执行结束取决于generator执行位置,跟next执行的位置无关
function *genThis(){
// true
console.log(this === window);
}
var g = genThis();
g.next();
var obj = {
a:1,
b:2,
gen: function*(){
// 1 2
console.log(this.a,this.b);
}
}
var g = obj.gen();
g.next();
Generator函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获。
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
上面代码中,遍历器对象i连续抛出两个错误。第一个错误被Generator函数体内的catch语句捕获。i第二次抛出错误,由于Generator函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了Generator函数体,被函数体外的catch语句捕获。
//一个回调函数处理错误的例子
foo('a', function (a) {
if (a.error) {
throw new Error(a.error);
}
foo('b', function (b) {
if (b.error) {
throw new Error(b.error);
}
foo('c', function (c) {
if (c.error) {
throw new Error(c.error);
}
console.log(a, b, c);
});
});
});
//用generator改写
function* g(){
try {
var a = yield foo('a');
var b = yield foo('b');
var c = yield foo('c');
} catch (e) {
console.log(e);
}
console.log(a, b, c);
}
Generator函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历Generator函数。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return("foo") // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
如果Generator函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行。
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers()
g.next() // { done: false, value: 1 }
g.next() // { done: false, value: 2 }
g.return(7) // { done: false, value: 4 }
g.next() // { done: false, value: 5 }
g.next() // { done: true, value: 7 }
如果在Generater函数内部,调用另一个Generator函数,默认情况下是没有效果的。
function* inner() {
yield 'hello!';
}
function* outer1() {
yield 'open';
yield inner();
yield 'close';
}
var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"
function* outer2() {
yield 'open'
yield* inner()
yield 'close'
}
var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
其实yield*可以看做是for-of的语法糖,当然这种语法糖也会丢失return的返回值
function* outer2() {
yield 'open'
yield* inner()
yield 'close'
}
function* outer2() {
yield 'open'
for(let v in inner())
yield v;
yield 'close'
}
async 可以声明一个异步函数,此函数需要返回一个 Promise 对象。await 可以等待一个 Promise 对象 resolve,并拿到结果。
function sleep(timeout) {
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve();
}, timeout);
});
}
(async function() {
console.log('Do some thing, ' + new Date());
await sleep(3000);
console.log('Do other things, ' + new Date());
})();