本文共 8160 字,大约阅读时间需要 27 分钟。
一是,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请求
内部的传递方式: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、可以知道是否执行到结尾
迭代器对象的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循环可以自动遍历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 5
Generator函数返回的迭代器对象,都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获。
既然我的文章是简单理解Generator函数,所以错误捕获直接跳过。
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语句也一律无视。
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定义一个异步函数 它其实是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定义一个异步函数 它其实是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后面的内容
如果是Promise实例。则await左侧的变量接收到的值,就是Promise实例resolve()的传参。
如果不是Promise实例。则会使用Promise.resolve将它转为Promise实例。
// 定义asyncasync 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左边的变量就得到什么。
如果代码中出现了错误,则会将异步函数返回值的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状态变化。会导致后续代码部分不会继续执行
转载地址:http://eoven.baihongyu.com/