协程是一种比线程更加轻量级的存在。可以把协程看成是跑在线程上的任务,一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程,比如当前执行的是 A 协程,要启动 B 协程,那么 A 协程就需要将主线程的控制权交给 B 协程,这就体现在 A 协程暂停执行,B 协程恢复执行;同样,也可以从 B 协程中启动 A 协程。通常,如果从 A 协程启动 B 协程,我们就把 A 协程称为 B 协程的父协程。
协程有点像函数,又有点像线程。它的运行流程大致如下。
第一步,协程 A 开始执行。
第二步,协程 A 执行到一半,进入暂停,执行权转移到协程 B。
第三步,(一段时间后)协程 B 交还执行权。
第四步,协程 A 恢复执行。
上面流程的协程 A,就是异步任务,因为它分成两段(或多段)执行。
举例来说,读取文件的协程写法如下。
1 2 3 4 5
functionasnycJob() { // ...其他代码 var f = yieldreadFile(fileA); // ...其他代码 }
functionco(gen) { if (typeof gen === 'function') gen = gen.apply(ctx, args); onFulfilled();
functiononFulfilled(res) { var ret; try { ret = gen.next(res); } catch (e) { returnreject(e); } next(ret); } functionnext(ret) { if (ret.done) returnresolve(ret.value); var value = toPromise.call(ctx, ret.value); if (value && isPromise(value)) return value.then(onFulfilled, onRejected); returnonRejected( newTypeError( 'You may only yield a function, promise, generator, array, or object, ' + 'but the following object was passed: "' + String(ret.value) + '"' ) ); } }
这儿,在给 co 传入一个generator函数后,co 会将其自动启动。然后调用onFulfilled函数。在onFulfilled函数内部,首先则是获取 next 的返回值。交由next函数处理。 而next函数则首先判断是否完成,如果这个 generator 函数完成了,返回最终的值。否则则将yield后的值,转换为Promise。最后,通过Promise的 then,并将onFulfilled函数作为参数传入。
1 2 3
if (value && isPromise(value)) { return value.then(onFulfilled, onRejected); }
而在generator中,yield句本身没有返回值,或者说总是返回undefined。而 next 方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。同时通过onFulfilled函数,则可以实现自动调用。这也就能解释为什么 co 基于Promise。且能自动执行了。
functionco(gen) { var ctx = this; var args = slice.call(arguments, 1);
// we wrap everything in a promise to avoid promise chaining, // 把传进来的所有东西都转为 promise // which leads to memory leak errors. // see https://github.com/tj/co/issues/180 returnnewPromise(function (resolve, reject) { // 执行 gen 函数 if (typeof gen === 'function') gen = gen.apply(ctx, args); // gen函数返回的 gen 指针不存在或 gen.next 不是函数(意味着 gen 不是 Generator函数) 则返回空值 if (!gen || typeof gen.next !== 'function') returnresolve(gen);
onFulfilled();
/** * @param {Mixed} res * @return {Promise} * @apiprivate * 把每次 yield 之后的异步函数的返回结果当做参数传回 generator 函数 * eg. let a = yeild b * 此时经过 onFulfilled 函数的处理 a === b() */ functiononFulfilled(res) { var ret; try { /** * gen.next(res),则是向generator函数传参数,作为yield的返回值 * ret 是 gen.next() 返回的对象 * --- * yield 语句本身没有返回值,或者说总是返回undefined。 * next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。 */ ret = gen.next(res); } catch (e) { returnreject(e); } // 每完成一次 yield,把返回的对象交给 next() 处理 next(ret); }
/** * Get the next value in the generator, * return a promise. * 把 gen.next() 返回对象中的 value 变为 promise * * @param {Object} ret * @return {Promise} * @apiprivate */
functionnext(ret) { /** * 如果这个generator函数完成了,返回最终的值 * 假设我们写的 generator函数没有 return someValue, 在所有yield完成后,调用next()会返回{valu: undefined, done: true} * 所以需要手动return一个值。这样最后的value才不是undefined */ if (ret.done) returnresolve(ret.value); // 这个generator函数还没结束, 就统一交给 toPromise() 处理 var value = toPromise.call(ctx, ret.value); // 这里value.then(onFulfilled, onRejected),实际上已经调用并传入了 onFulfilled, onRejected 两个参数。 // 把 onFulfilled 函数传入 value if (value && isPromise(value)) return value.then(onFulfilled, onRejected); returnonRejected( newTypeError( 'You may only yield a function, promise, generator, array, or object, ' + 'but the following object was passed: "' + String(ret.value) + '"' ) ); } }); }
// 根据点击 btn 的 class 属性绑定不同的事件回调 bindEvent() { this.el.addEventListener('click', event => { let el = event.target; let id = el.dataset.id; if (el.classList.contains('todo-delete')) { deleteTodo(id); } elseif (el.classList.contains('todo-update')) { updateTodo(el, id); } elseif (el.classList.contains('todo-add')) { addTodo(el); } });
// 点击 add 时,把新的 todo 添加到 model 中 constaddTodo = el => { let input = el.parentElement.querySelector('.input-add'); let value = input.value; if (value !== '') { let data = this.model.data; data.push({ id: data.length, value: value, }); this.model.data = data; } };
// 点击 delete 时,把对应 todo 从 model 中删除 constdeleteTodo = id => { this.model.data = this.model.data.filter(todo => { return id !== String(todo.id); }); };