Generator与yield
Generator函数跟普通函数的写法区别:
一是,function关键字与函数名之间有一个星号;
二是,函数体内部使用yield语句,定义不同的内部状态(yield在英语里的意思就是“产出”)。
简单的Generator函数如下:
// 通过定义Generator函数 来发送两次AJAX请求
function * Gen() {
// 发送第一次ajax
$.ajax({
url: "/ajax1",
dataType: "json",
success: function(data) {
console.log("发送完毕ajax1");
g.next(); // 当第一次ajax请求结束之后,就发送第二次
}
});
yield;
$.ajax({
url: "/ajax2",
dataType: "json",
success: function(data) {
console.log("发送完毕ajax2");
}
});
}
// 调用
let g = Gen();
g.next(); // 发送了第一次ajax请求从Generator函数内部向外部返回数据
内部的传递方式:yield关键字后面可以跟内容。这些内容会被返回出去。
外部的接收方式:Gen.next() 会返回一个对象,对象中有value有done 其中value就是yield给返回出来的内容。
// 定义Generator函数
function * g() {
yield 'a';
yield 'b';
yield 'c';
return 'ending';
}
// 调用
var gen = g();
gen.next(); // 返回Object {value: "a", done: false}
gen.next()返回一个非常非常简单的对象{value: "a", done: false},'a'就是g函数执行到第一个yield语句之后得到的值,false表示g函数还没有执行完,只是在这暂停。
如果再写一行代码,还是gen.next();,这时候返回的就是{value: "b", done: false},说明g函数运行到了第二个yield语句,返回的是该yield语句的返回值'b'。返回之后依然是暂停。
再写一行gen.next();返回{value: "c", done: false},再写一行gen.next();,返回{value: "ending", done: true},这样,整个g函数就运行完毕了。

所以现在可以看出,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield语句(或return语句)为止。换言之,Generator函数是分段执行的,yield语句是暂停执行的标记,而next方法可以恢复执行。
总之,每调用一次Generator函数,就返回一个迭代器对象,代表Generator函数的内部指针。以后,每次调用迭代器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield语句后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。
所以可以看出,Generator 函数的特点就是:
1、分段执行,可以暂停
2、可以控制阶段和每个阶段的返回值
3、可以知道是否执行到结尾
yield语句
迭代器对象的next方法的运行逻辑如下。
(1)遇到yield语句,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。
(3)如果没有再遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。
yield语句与return语句既有相似之处,也有区别。
相似之处在于,都能返回紧跟在语句后面的那个表达式的值。
区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield语句。正常函数只能返回一个值,因为只能执行一次return;Generator函数可以返回一系列的值,因为可以有任意多个yield。从另一个角度看,也可以说Generator生成了一系列的值,这也就是它的名称的来历(在英语中,generator这个词是“生成器”的意思)。
注意:yield语句只能用于function*的作用域,如果function*的内部还定义了其他的普通函数,则函数内部不允许使用yield语句。
for...of循环
for...of循环可以自动遍历Generator函数时生成的Iterator对象,且此时不再需要调用next方法。for...of循环的基本语法是:
//其中foo()是迭代器对象,可以把它赋值给变量,然后遍历这个变量。
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
let a = foo();
for (let v of a) {
console.log(v);
}
// 1 2 3 4 5Generator.prototype.throw()
Generator函数返回的迭代器对象,都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获。
既然我的文章是简单理解Generator函数,所以错误捕获直接跳过。
Generator.prototype.return()
Generator函数返回的迭代器对象,还有一个return方法,可以返回给定的值,并且终结遍历Generator函数。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.return('foo')); // { value: "foo", done: true }
console.log(g.next()); // {value: undefined, done: true}就是说,return的参数值覆盖本次yield语句的返回值,并且提前终结遍历,即使后面还有yield语句也一律无视。

Co函数的使用 结合promise与Generator 优化代码
function co(gen) {
// 返回值是一个Promise
return new Promise(function(resolve, reject) {
// 在promise中执行Generator函数
const g = gen();
// 定义一个函数
function next(nextFun) {
// 预先定义变量
let f = null;
// 尝试执行
try {
f = nextFun();
} catch(e) {
// 如果有错误,则被捕获到的错误信息被传递到rejected回调函数中
return reject(e);
}
// 判定是否执行结束
if (f.done) {
// 如果已经结束,此时才会执行返回的Promise实例的成功时的回调函数
return resolve(f.value);
}
// 如果没有结束,就将这一次的返回结果强行制作成一个Promise实例
Promise.resolve(f.value).then(function(result) {
// 如果f.value是一个值 则result就是该值
// 如果f.value是一个Promise实例 则result是它成功时的传递的参数
// 如果f.value是一个具备then方法的对象 则result是这个对象的then方法执行完毕之后的成功时的返回值
// 不论如何,result一定是成功时才会有的
// 也即是说,这个函数一定是在成功时才会执行的
// 如果成功了,就调用next继续下一次的执行
next(function() {
// 继续下一次的执行
return g.next(result);
});
}, function(result) {
// 如果f.value是一个值,则此函数不会执行
// 如果f.value是一个Promise实例 则result是它失败时的传递的值
// 如果f.value是一个具备then方法的对象 则result是这个then方法执行失败时的传递的信息
// 不论如何,result一定是失败时才会有的
// 也即是说,这个函数一定是在失败的时候才会执行的
// 如果失败了,就直接终止函数的执行
next(function(result) {
return g.throw(result);
});
})
}
// 开启第一次的next执行
next(function() {
return g.next(undefined);
});
})
}通过co函数来使用: 条件,yield之后跟的一定要是Promise实例。
//引入封装的co函数
// 定义任务
function taskOne() {
return new Promise(function(resolve, reject) {
$.ajax({
url: "/ajax1",
dataType: "json",
success: function(data) {
resolve(123);
}
});
});
}
function taskTwo() {
return new Promise(function(resolve, reject) {
$.ajax({
url: "/ajax2",
dataType: "json",
success: function(data) {
resolve(data);
}
});
});
}
// 定义Generator函数
function * Gen() {
let data = yield taskOne();
console.log(data);
let data1 = yield taskTwo();
console.log(data1);
}
co(Gen);再看这种写法:
每一个任务中,都与其它任务不干涉。 很干净
async和await
async定义一个异步函数 它其实是Generator的语法糖
await定义一个等待函数 它其实是yield的语法糖
async定义一个异步函数 它其实是Generator的语法糖
定义语法: async function fun() {}
当定义了一个async函数之后,此时,函数的性质就变了。
普通函数的返回值,是由函数内的return关键字决定。
async函数的返回值,总是返回Promise实例。
// 语法糖:指的是对高级复杂特性的一种简化使用方式
async function taskOne() {
console.log(1);
await new Promise(function(resolve, reject) {
setTimeout(function() {
console.log("等待了3秒");
resolve();
}, 3000);
});
console.log(2);
await new Promise(function(resolve, reject) {
setTimeout(function() {
console.log("等待了3秒");
resolve();
}, 3000);
})
console.log(3);
}
// 执行这个异步函数
taskOne();async
async定义一个异步函数 它其实是Generator的语法糖
定义语法: async function fun() {}
当定义了一个async函数之后,此时,函数的性质就变了。
普通函数的返回值,是由函数内的return关键字决定。
async函数的返回值,总是返回Promise实例。
// 定义普通函数
function fun() {
}
// 调用普通函数
let f = fun();
console.log(f); // undefined
// 定义异步函数
async function func() {
}
let f1 = func();
console.log(f1); // Promise的实例它的状态其实是由异步函数内的执行情况来决定。
如果一切正常,该函数内的所有代码能够顺利执行完毕。则状态变为resolved
如果有异常,则状态变为rejected
它的结果是由异步函数的返回值决定
await
await后面的内容
如果是Promise实例。则await左侧的变量接收到的值,就是Promise实例resolve()的传参。
如果不是Promise实例。则会使用Promise.resolve将它转为Promise实例。
// 定义async
async function fun() {
let a = await 123;
}此时,没有任何异常。但是如果将async去掉
![]()
await表示“等待”。当执行到await的时候,会交出线程的控制权。
// 定义async函数
async function fun() {
console.log(123);
await 123;
console.log(321);
}
fun();
console.log("哈哈哈哈");
await后面跟的是Promise实例,Promise实例如果成功,则await左边的变量可以得到数据。得到的值就是resolve时的数据。
// 验证
async function fun() {
let a = await new Promise(function(resolve, reject) {
resolve("hello world");
});
console.log(a);
}
fun();注:resolve执行时传递的是一个字符串。所以await左边的变量a得到的就是一个字符串。
规则: resolve接收什么参数,await左边的变量就得到什么。
错误处理 catch try catch
如果代码中出现了错误,则会将异步函数返回值的Promise实例状态变为rejected并传递错误。
如果某一个await后面的Promise实例状态变为rejected。则也会将异步函数返回值的Promise实例状态变为rejected,并传递错误。
// 定义async函数
async function fun() {
throw new Error("123"); // 这一条会报错
let a = await new Promise(function(resolve, reject) {
setTimeout(function() {
reject(123); // 这一条也会报错
}, 3000);
});
}
// 如果某一个await后面的Promise实例状态变为了rejected
// 则直接中止函数的执行并将异步函数Promise实例的状态变为rejected
let p = fun();
p.catch(function(err) {
console.log(err);
});
还有另外一种处理方式:
// 定义async函数
async function fun() {
// throw new Error("123");
try {
let a = await new Promise(function(resolve, reject) {
setTimeout(function() {
reject(123);
}, 3000);
});
}catch(err) {
console.log(err);
}
// 后续代码……
}
// 如果某一个await后面的Promise实例状态变为了rejected
// 则直接中止函数的执行并将异步函数Promise实例的状态变为rejected
let p = fun();
p.catch(function(err) {
console.log(err);
});因为这里是在异步函数内部处理了错误,所以异步函数中的后续代码部分会继续执行。但如果没有try……catch。则会影响到异步函数的返回值的Promise状态变化。会导致后续代码部分不会继续执行