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

这里的 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'

首次调用 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 }

对我们来说有趣的部分是 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);

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

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

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaScript 协程函数本身并不是实现了 thenable 接口,而是返回了一个 Promise 对象,这个 Promise 对象实现了 thenable 接口。 协程函数使用 async/await 语法来简化异步编程,但本质上它还是基于 Promise 对象实现的。在协程函数,使用 await 关键字来等待异步操作的完成,并使用 try/catch 语句来处理 Promise 对象的解析和拒绝结果。协程函数会返回一个 Promise 对象,这个 Promise 对象实现了 thenable 接口,可以使用 then 方法进行链式调用。 下面是一个简单的协程函数的例子: ```javascript async function myCoroutine() { try { const result1 = await someAsyncOperation(); const result2 = await anotherAsyncOperation(result1); return result2; } catch (error) { console.error(error); } } myCoroutine().then( result => console.log('Resolved:', result), error => console.log('Rejected:', error) ); ``` 在上面的例子,myCoroutine 函数使用 async 关键字定义,表示这是一个异步函数。在函数,我们使用 await 关键字来等待异步操作的完成,并使用 try/catch 语句来处理 Promise 对象的解析和拒绝结果。在函数执行完成后,我们可以使用 then 方法来处理 Promise 对象的解析和拒绝结果,达到异步编程的效果。 总结来说,JavaScript 协程函数本身并不是实现了 thenable 接口,而是返回一个 Promise 对象,这个 Promise 对象实现了 thenable 接口,可以使用 then 方法进行链式调用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值