Advertisement

Javascript 中的协程是怎么一回事

阅读量:

协程 是一种函数,其执行可以被暂停和恢复,可能传递一些数据。它们恰好适用于实现涉及不同任务/函数之间合作的各种模式,比如异步流。

在 JavaScript 中

在 JavaScript 中,你可以使用生成器函数来实现(类似)协程。你可能已经使用生成器函数来实现迭代器和序列。

复制代码
    function *integers(){
    let n = 0;
    while(true){
        yield ++n;
    }
    }
    
    const sequence = integers();
    
    console.log(sequence.next().value); // > 1
    console.log(sequence.next().value); // > 2
    console.log(sequence.next().value); // > 3
    console.log(sequence.next().value); // > 4
    
    
    javascript
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-07-13/F1J9D84IUf0PqAYOezuETQpSgjnw.png)

这里的 while(true) 很有趣(而且完全没问题),因为它证明了生成器正在惰性地被评估。当你调用 next 函数时,实际上是执行生成器,直到下一个 yield 语句。yield 右侧表达式的结果成为迭代器结果的 value,然后生成器函数暂停。

我们通常不知道的是,当恢复例程的执行时,你可以向 next 函数传递数据,这样做的效果是将该数据分配给语句“左侧”的任何变量:

复制代码
    function *generator() {
    while(true){
        const action = yield;
        console.log(action)
    }
    }
    
    const routine = generator();
    routine.next();
    routine.next('increment'); // > 'increment'
    routine.next('go-left'); // > 'go-left'
    
    
    javascript
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-07-13/OHoCuTVng52jfYpPFk8ZxtDABhMs.png)

首次调用 next 显然不能接收任何数据,因为例程尚未暂停。

双向示例

虽然通常你会将生成器用作数据的生产者或接收者,但你可以同时在两个方向上使用它。请注意,管理起来可能会令人困惑和复杂,但它非常方便用于实现某些模式。

看以下类似于 “Redux” 的状态机示例:

复制代码
    function* EventLoop({reducer, state}) {
    while (true) {
        const action = yield state; // 哇!
        state = reducer(state, action);
    }
    }
    
    const createEventLoop = ({reducer, state}) => {
    const eventLoop = EventLoop({reducer, state});
    eventLoop.next();
    return (action) => eventLoop.next(action).value;
    };
    
    const createSubscribable = () => {
    const eventName = 'state-changed';
    const eventTarget = new EventTarget();
    
    const notify = () => eventTarget.dispatchEvent(new CustomEvent(eventName));
    const subscribe = (listener) => {
        eventTarget.addEventListener(eventName, listener);
        return () => unsubscribe(listener);
    };
    const unsubscribe = (listener) =>
        eventTarget.removeEventListener(eventName, listener);
    
    return {
        unsubscribe,
        subscribe,
        notify
    };
    };
    
    const createStore = ({reducer, initialState}) => {
    let state = initialState;
    
    const {notify, ...subscribable} = createSubscribable();
    
    const dispatch = createEventLoop({reducer, state});
    
    return {
        ...subscribable,
        getState() {
            return structuredClone(state);
        },
        dispatch(action) {
            state = dispatch(action);
            notify();
        }
    };
    };
    
    const store = createStore(
    {
        reducer: (state, action) => {
            switch (action.type) {
                case 'increment':
                    return {
                        ...state,
                        count: state.count + 1,
                    };
                case 'decrement':
                    return {
                        ...state,
                        count: state.count - 1,
                    };
                default:
                    return state;
            }
        },
        initialState: {
            count: 0,
        }
    }
    );
    
    store.subscribe(() => console.log(store.getState()));
    
    store.dispatch({
    type: 'increment'
    }); // 记录 { count: 1 }
    store.dispatch({
    type: 'increment'
    }); // 记录 { count: 2 }
    store.dispatch({
    type: 'decrement'
    }); // 记录 { count: 1 }
    
    
    javascript
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-07-13/qVMSUa0enLxkFC19EuAhO2KjYoZ8.png)

对我们来说有趣的部分是 EventLoop 例程,当暂停时,它返回当前状态,并在恢复时接收下一个要处理的动作。createEventLoop 函数隐藏了我们使用协程来实现状态机的事实,使其成为实现的一个细节。然而,由于协程的存在,整体解决方案保持简洁而相当简单。

异步流示例

在前面的示例中,我们看到了如何使用协程模拟事件循环。在下面的示例中,我们将看到一种不同类型的 “协同多任务”,使用相同语义的异步工作流程,正如常规的 async 函数一样(使用 await 关键字)。

复制代码
    const co = (genFn) => (...args) => {
      const gen = genFn(...args);
    
      // 没有数据传递给 next,因为例程尚未暂停
      return next();
    
      function next(data) {
    const { value, done } = gen.next(data);
    
    if (done) {
      return value;
    }
    
    // 非 Promise 值
    if (value?.then === undefined) {
      return next(value);
    }
    
    // 我们恢复例程,并将解决的值赋给 "yield"
    return value.then(next);
      }
    
    };
    
    const fn = co(function* (arg) {
      let value = yield asyncTask(arg);
      value = yield otherAsyncTask(value);
      return value;
    });
    
    fn(42).then(console.log);
    
    
    javascript
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-07-13/wSedCsFYD159vcVX0JyZkgTQHjAp.png)

其背后的思想非常简单:每当主要异步函数委托任务给另一个函数时,它就会被暂停。如果该函数本身是异步的,我们会等待挂起的 Promise 解析,然后使用解析后的值恢复主例程。这与 async 函数非常相似,只是你用 yield 替换了内置的 await 关键字。

如果你看完还是对coroutine一知半解,不急,关注博主以后会更新更多示例让你弄明白如何高效的使用javascript中的coroutine.

全部评论 (0)

还没有任何评论哟~