JavaScript 异步编程:async、await 的实现原理

#javascript

目录

1. 预备知识

1.1. 回调函数

1.2. Promise

1.3. generate 协程

function* numberGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = numberGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3

1.4. Thunk 函数与 thunkify

  • Thunk 是一种将多参数函数转换为单参数的偏应用函数的方法
  • Thunk通常用于将回调风格的函数转换为 promise 风格
// 普通的回调风格函数
function fetchData(callback) {
    // 模拟异步操作
    setTimeout(() => callback(null, 'data'), 1000);
}

// Thunk 函数
function thunkify(fn) {
    return function(...args) {
        return function(callback) {
            return fn.call(this, ...args, callback);
        }
    }
}

const thunkedFetchData = thunkify(fetchData);
const thunk = thunkedFetchData();
thunk((err, data) => console.log(data));

1.5. co 函数

  • co 是一个著名的 npm 包,它是一个生成器执行器,允许你使用同步的方式编写异步代码
  • 它可以自动执行生成器函数,并处理 yield 的 Promise、thunks 或 数组/对象。
const co = require('co');

co(function* () {
    const result = yield Promise.resolve(1);
    return result;
}).then(value => console.log(value));

2. 生成器 vs async/await

  • 生成器是更底层的机制,需要手动迭代或使用执行器。
  • async/await 是建立在生成器和 Promise 之上的高级语法,提供了更简洁的异步编程方式。
  • async/await 可以看作是生成器和自动执行器的语法糖。

3. Thunk vs Promise

  • Thunk 主要用于将回调风格的函数转换为更易于操作的形式。
  • Promise 提供了一种标准的异步操作表示方法,具有更丰富的功能(如 .then()、.catch() 等)。

4. co 函数 vs async/await

  • co 函数是 async/await 出现之前的一种解决方案,用于简化生成器的使用。
  • async/await 可以看作是 co 函数语言级实现,提供了更原生和简洁的语法。

5. 协程 vs 其他概念

  • 协程是一个更广泛的概念,生成器是 JavaScript 中协程的一种实现。
  • async/await、Thunk、co 函数都可以看作是基于协程思想的不同层次的抽象和实现

6. co 函数的最简实现

co 函数的核心功能是自动执行生成器函数,将 yield 的值转换为 Promise,并处理异步流程。

以下是一个最简单的 co 函数实现:

function co(gen) {
    // 如果传入的是生成器函数,先执行它得到生成器对象
    const generator = typeof gen === 'function' ? gen() : gen;
    
    // 返回一个 Promise
    return new Promise((resolve, reject) => {
        // 定义递归处理函数
        function step(nextValue) {
            let result;
            
            try {
                // 执行下一步,传入上一步的值
                result = generator.next(nextValue);
            } catch (e) {
                // 如果执行出错,reject Promise
                return reject(e);
            }
            
            // 如果生成器执行完毕,resolve 最终值
            if (result.done) {
                return resolve(result.value);
            }
            
            // 将 yield 的值转换为 Promise
            Promise.resolve(result.value)
                .then(value => step(value))  // 继续执行下一步
                .catch(reject);              // 处理错误
        }
        
        // 开始执行
        step();
    });
}

6.1. 使用建议

  • 在现代JavaScript中,推荐使用 async/await 而不是 co
    • async/await 可以看作是这种模式的语言级实现
  • 理解 co 的实现有助于深入理解 async/await 的工作原理
  • 在需要支持旧版本环境时,co 仍然是一个有用的工具