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 則能將異步編程寫得更簡潔優雅。

相關文章
相關標籤/搜索