为了账号安全,请及时绑定邮箱和手机立即绑定

JavaScript异步编程之Generator与async

Generator 函数

基本概念

Generator 译名生成器,是一种生成遍历器的函数。生成器函数与普通函数相比,有两个特征:一是,function关键字与函数名之间有一个星号;二是,函数体内部使用 yield 表达式。

例子:

function* fn() {
    yield 0;
    yield 1;
}

*不能使用箭头函数创建生成器函数。

调用生成器函数后,创建的是一个遍历器对象,每次调用遍历器的 next() 方法,就会执行一条 yield 语句,然后自动停止执行,直到再次调用 next() 方法才会继续执行。 next() 方法返回对象的 value 属性(当前 yield 表达式后面的任何值或表达式计算结果),以及 done 属性(false,表示遍历还没有结束)。

例子:

function* fn() {
    yield 0;
    yield 1;
};
//创建遍历器
let iterator = fn();
console.log(iterator.next()); //输出:{value: 0, done: false}
console.log(iterator.next()); //输出:{value: 1, done: false}
console.log(iterator.next()); //输出:{value: undefined, done: true}
console.log(iterator.next()); //输出:{value: undefined, done: true}

*第 3 次调用 next() 方法,返回对象的 value 属性为 undefined,done 属性为 true,说明遍历器已经运行完毕,以后再调用 next() 方法,返回的都是这个值。

next() 方法可以带一个参数,该参数会覆盖上一个 yield 表达式的返回值。

例子:

function* fn(x) {
    let y = yield x + 1; //x=1 => 1+1
    let z = yield y + 1; //y=3 => 3+1
    return x + y + z; //x=1,y=3,z=4 => 1+3+4
}
let iterator = fn(1);
console.log(iterator.next()); // 输出:{value: 2, done: false}
console.log(iterator.next(3)); // 输出:{value: 4, done: false}
console.log(iterator.next(4)); // 输出:{value: 8, done: true}

遍历器中的代码执行过程示意图:
图片描述

*第一次调用 next() 方法时传入任何参数都没有效果,因为 next() 方法的参数会替代上一次 yield 表达式的返回值,而第一次调用 next() 方法前并没有执行任何 yield 语句。

遍历器的常用方法

return() 方法

return() 方法将yield 表达式替换成一个return语句,并返回给定的值。

例子:

let fn = function*() {
    yield 1;
    yield 2;
    yield 3;
}
let iterator = fn();
console.log(iterator.next()); //输出:{value: 1, done: false}
console.log(iterator.return(9)); //输出:{value: 9, done: true}
console.log(iterator.next()); //输出:{value: undefined, done: true}

throw() 方法

throw() 将 yield 表达式替换成一个 throw 语句,并返回给定的错误信息。

例子:

let fn = function*() {
    yield 1;
    yield 2;
    yield 3;
}
let iterator = fn();
console.log(iterator.next()); //输出:{value: 1, done: false}
console.log(iterator.throw(new Error('出错了!'))); //输出:Error: 出错了!
onsole.log(iterator.next()); //没有输出

yield* 表达式

yield* 表达式用来在一个生成器函数里面,遍历拥有遍历器接口(Symbol.iterator)的数据结构。

例子:

数组

let fn = function*() {
    yield 1;
    yield*[2, 3];
    yield 4;
}
let iterator = fn();
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: undefined, done: true}

字符串

let fn = function*() {
    yield*"love";
}
let iterator = fn();
console.log(iterator.next()); //输出:{value: "l", done: false}
console.log(iterator.next()); //输出:{value: "o", done: false}
console.log(iterator.next()); //输出:{value: "v", done: false}
console.log(iterator.next()); //输出:{value: "e", done: false}
console.log(iterator.next()); //输出:{value: undefined, done: true}

遍历器对象

let fn1 = function*() {
    yield 2;
    yield 3;
}
let fn2 = function*() {
    yield 1;
    yield* fn1();
    yield 4;
}
let iterator = fn2();
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: undefined, done: true}

生成器函数与异步编程

生成器函数与 Promise 对象结合使用,可以达到一种书写形式上的“化异步为同步”的效果。
假如现在有两个请求,第二个请求的参数是第一个请求的返回值,首先看下面Promise 异步编程的传统写法:

例子:

//异步请求数据的函数
function p1(time) {
    return new Promise(function(resolve, reject) {
        setTimeout(() => {
            resolve(1);
        }, time);
    })
};

//p2 接收到 p1 的数据才能进行下一次异步请求
function p2(n, time) {
    return new Promise(function(resolve, reject) {
        setTimeout(() => {
            resolve(n + 1);
        }, time);
    })
};

//Promise 的链式编写模式
p1(1000).then((data) => {
    console.log(data);
    return p2(data, 1000);
}).then((data) => {
    console.log(data);
    return "end";
}).then((data) => {
    console.log(data)
});
//输出:1 2 end

由于生成器函数的暂停执行的效果:yield 语句会暂停当前函数的执行,并且等待下一次调用 next()方法。这意味着可以把第一个请求写在 yield 关键字后面,待取得数据之后再一次调用 next()方法,进行第二次请求。

例子:

//异步请求数据的函数
function p1(time) {
    return new Promise(function(resolve, reject) {
        setTimeout(() => {
            resolve(1);
        }, time);
    })
};

//p2 接收到 p1 的数据才能进行下一次异步请求
function p2(n, time) {
    return new Promise(function(resolve, reject) {
        setTimeout(() => {
            resolve(n + 1);
        }, time);
    })
};

//使用生成器函数的编写模式
function* iterator() {
    //第一次请求
    let data = yield p1(1000);
    //第二次请求
    yield p2(data, 1000);
    console.log("end");
};

//手动执行生成器函数
let it = iterator();
it.next().value.then((data) => {
    console.log(data);
    it.next(data).value.then((data) => {
        console.log(data);
        it.next();
    })
});
//输出:1 2 end

上面的代码中,使用生成器函数编写的代码,类似于同步任务的书写方式:第一次请求的返回值会为变量 data 赋值,而接下来会带着已经赋值的变量 data 发出第二次请求。这种书写方式的好处是逻辑很清晰。 上面的代码中,我们手动编写了执行生成器函数的过程,我们也可以写一个函数,自动去执行生成器函数:

例子:

//异步请求数据的函数
function p1(time) {
    return new Promise(function(resolve, reject) {
        setTimeout(() => {
            resolve(1);
        }, time);
    })
};

//p2 接收到 p1 的数据才能进行下一次异步请求
function p2(n, time) {
    return new Promise(function(resolve, reject) {
        setTimeout(() => {
            resolve(n + 1);
        }, time);
    })
};

//使用生成器函数的编写模式
function* iterator() {
    //第一次请求
    let data = yield p1(1000);
    //第二次请求
    yield p2(data, 1000);
    console.log("end");
};

//自动执行生成器函数
function run(iterator) {
    let it = iterator();
    function next(data) {
        var result = it.next(data);
        if (!result.done) {
            result.value.then((data) => {
                console.log(data);
                next(data);
            })
        }
    }
    next();
};
run(iterator);

async 函数

基本概念

Generator 函数(生成器)并非解决异步编程的完美方案,最大的缺点是必须手动调用 next() 方法一步一步的去执行,或者依赖外部文件(co 函数库等)去执行。而 async 函数就是将 Generator 函数和自动执行器,包装在一个函数里。

基本用法

在形式上,async 函数将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await。
在功能上,async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。async 函数中,return 对应着 Promise 对象中 resolve 函数,throw 对应着 Promise 对象中 reject 函数。

例子:

async function _Async(n) {
    if (n > 5) {
        return ">5" //相当于 resolve(">5")
    } else {
        throw "<5" //相当于 reject("<5")
    }
}

_Async(6).then(function(data) {
    console.log(data); //输出:>5
});

_Async(4).catch(function(data) {
    console.log(data); //输出:<5
});

*可以使用箭头函数创建 async 函数。

上例中并不能体现出 async 函数在异步编程中的优势,async 函数还需要结合 await 关键字一起使用。 await 会暂停当前 async 函数的执行,等待后面的 Promise 的计算结果返回以后再继续执行当前的 async 函数。 如果说 Generator 函数与 Promise 组合使用,是对 Promise 异步编程的改善,那么 async/await 是对这一组合的又一次升级。

例子:

//异步请求数据的函数
function p1(time) {
    return new Promise(function(resolve, reject) {
        setTimeout(() => {
            resolve(1);
        }, time);
    })
};

//p2 接收到 p1 的数据才能进行下一次异步请求
function p2(n, time) {
    return new Promise(function(resolve, reject) {
        setTimeout(() => {
            resolve(n + 1);
        }, time);
    })
};

//async 函数
async function _Async(time) {
    let data = await p1(time).then(val => {
        console.log(val);
        return val;
    });
    await p2(data, time).then(val => {
        console.log(val);
    });
    return "end";
};
_Async(1000).then(val => {
    console.log(val);
});
//输出:1 2 end

正常情况下,await 命令后面是一个 Promise 对象,如果是其他的异步操作(例如 setTimeout),那它还是会继续向下执行,不会等待;如果是基本类型数据,就直接返回对应的值。

例子1:
其他异步操作

let _Async = (async function(time) {
    await setTimeout(() => {
        console.log(1)
    }, time);
    console.log(2);
})(1000);
//输出:2 1

例子2:
基本类型数据

let _Async = async function() {
    console.log(1);
    return await 2; //等同于 return 2
};
_Async().then(v => {
    console.log(v);
});
//输出:1 2

如有错误,欢迎指正,本人不胜感激。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 2
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消