Generator与yield 语法糖 async和await

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 5

Generator.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状态变化。会导致后续代码部分不会继续执行


版权声明:本文为qq_41328247原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。