JS异步编程之Generator

前言

ES6 中提出一个叫生成器(Generator)的概念,执行生成器函数,会返回迭代器对象(Iterator),这个迭代器对象能够遍历函数内部的每个状态。javascript

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

// 经过执行生成器返回迭代器对象
var helloWorldIterator = helloWorldGenerator();

helloWorldIterator.next(); 
// { value: "hello", done: false }

helloWorldIterator.next(); 
// { value: "world", done: false }

helloWorldIterator.next(); 
// { value: "ending", done: true }

helloWorldIterator.next(); 
// { value: undefined, done: true }
复制代码

迭代器对象经过调用 next() 方法,遍历下一个内部状态,生成一个值,这也是 Generator 名字的由来。java

1、generator 的异步调用

每当 generator 生成一个值,程序会挂起,自动中止执行,随后等待下一次执行,直到下一次调用 next() 方法,但并不影响外部主线程其余函数的执行。编程

generator 让函数执行过程有了同步的特色,基于这个特色,咱们将异步调用和生成器结合起来:api

前后打印 "hello China!", "hello Wolrd!", "hello Earth!";异步

function fetch(word) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("hello " + word);
        }, 2000)
    })
}

function* gen() {
    try {
        const api1 = yield fetch("China!");
        console.log(1);

        const api2 = yield fetch("World!");
        console.log(2);
        
        const api3 = yield fetch("Earth!");
        console.log(3);
    } catch(error) {
        console.log(error);
    }
    
}

const iterator = gen();  // 返回迭代器对象

const result1 = iterator.next().value;

result1
.then(res1 => {
    console.log(res1)
    return iterator.next().value;
})
.then(res2 => {
    console.log(res2)
    return iterator.next().value;
})
.then(res3 => {
    console.log(res3)
    return iterator.next().value;
})
复制代码

每次调用迭代器的 next 方法,会返回一个 Promise 对象,经过 Promise 对象状态从 pending 转移到 fullfilled 状态,能够在 .then() 方法后执行下一个异步方法。async

2、Generator 自执行

从第二节中能够看出,Generator 每次调用异步方法,都要手动执行一次 iterator.next(),经过递归 iterator.next() 咱们就不用再手动执行 next() 方法了。异步编程

function fetch(word) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("hello " + word);
        }, 2000)
    })
}

function* gen() {
    try {
        const api1 = yield fetch("China!");
        console.log(1);

        const api2 = yield fetch("World!");
        console.log(2);
        
        const api3 = yield fetch("Earth!");
        console.log(3);
    } catch(error) {
        console.log(error);
    }
    
}

function co(gen) {
    const g = gen();
    
    function next(data) {
        const result = g.next(data);
        if(result.done) return;
        result.value.then(data => {
            console.log(data);
            next(data);
        })
    }
    
    next();
}

co(gen);
复制代码

3、在迭代器中抛出错误

迭代器除了能在 next() 方法中传递参数外,还能经过 iterator.throw 方法捕捉到错误,从而加强了异步编程的错误处理能力。函数

function fetch(word) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("hello " + word);
        }, 2000)
    })
}

function* gen() {
    try {
        const api1 = yield fetch("China!");
        console.log(1);

        const api2 = yield fetch("World!");
        console.log(2);
        
        const api3 = yield fetch("Earth!");
        console.log(3);
    } catch(error) {
        console.log(error);  // Error: 抛出一个错误
    }
    
}

const iterator = gen();  // 返回迭代器对象

const result1 = iterator.next().value;

result1
.then(res1 => {
    console.log(res1)
    iterator.throw(new Error("抛出一个错误"))
    return iterator.next().value;
})
.then(res2 => {
    console.log(res2)
    return iterator.next().value;
})
.then(res3 => {
    console.log(res3)
    return iterator.next().value;
})
复制代码

调用了 iterator.throw 方法后,错误就能被抛出被生成器中的中的 try catch 捕捉到,且阻止后面的代码继续执行。fetch

4、Generator 应用场景

Generator 最使人兴奋的地方在于,生成器中的异步方法看起来更像是同步执行。很差的地方在于执行过程比较生硬。ui

总结

Generator 生成具备 Symbol.iterator 属性的迭代器对象,迭代器具备 next 方法,可以无阻塞地将代码挂起,下次调用 .next() 方法再恢复执行。

用 Generator 实现异步编程只是一个 hack 用法,Generator 的语法糖 async & await 则能将异步编程写得更简洁优雅。

相关文章
相关标签/搜索