[自己动手写]使用Generator的异步库

栏目: JavaScript · 发布时间: 5年前

内容简介:使用输出为:如果代码为:

使用 function* 的形式申明Generator函数,并使用 yield 关键字返回值;执行Generator函数会返回一个遍历器对象,遍历器对象带有 next 方法,执行后返回的值包含 valuedone 属性:

const g = function* g() {
  yield 'hello';
  yield 'world';
}

const iterable = g();
let v;
do {
  v = iterable.next();
  console.log(v.value, v.done);
} while(!v.done)
复制代码

输出为:

hello false
world false
undefined true
复制代码

如果代码为:

const g = function* g() {
  yield 'hello';
  return 'world';
}
复制代码

则输出为:

hello false
world true
复制代码

Generator中可以使用 for 循环、 while 循环等:

const g = function* g() {
  const arr = ['hello', 'world']
  for (let item of arr) {
    yield item
  }
}
复制代码

我们可以用 for of 遍历迭代器:

for (let item of g()) { console.log(item) }
// hello
// world
复制代码

可以通过设置 [Symbol.iterator] 来创造迭代器:

const iterable = {
  [Symbol.iterator]: function* () {
    const arr = ['hello', 'world']
    for (let item of arr) {
      yield item
    }
  }
};
for (let item of iterable) { console.log(item) }
复制代码

Generator可以通过 next 传入参数(从第二次调用 next 起,后面会解释为什么从第二次起):

const g = function* g() {
  const arr = [];
  let v
  while(v = yield arr) { arr.push(v) }
}

const iterable = g();
console.log(iterable.next().value); // []
console.log(iterable.next(1).value); // [1]
console.log(iterable.next(2).value); // [1, 2]
复制代码

可以使用 throw 来抛出错误(在 throw 前确保先执行一次 next ):

const g = function* g() {
  const arr = [];
  let v
  while(true) {
    try {
      v = yield arr
      arr.push(v)
    } catch  (err) {
      console.log('err', err)
    }
  }
}

const iterable = g();
console.log(iterable.next().value);
console.log(iterable.next(1).value);
iterable.throw('whoops');
console.log(iterable.next(2).value);

// []
// [1]
// err, whoops
// [1, 2]
复制代码

也可以由Generator抛出错误,外部捕获:

const g = function* g() {
  const hasErr = yield 1
  if (hasErr) throw 'whoops'
  yield 2
}

const iterable = g();
iterable.next();
try {
   iterable.next(true);
} catch(err) {
  console.log(err);
}
复制代码

可以用 return 提前结束Generator:

const g = function* g() {
  yield 1;
  yield 2;
  yield 3;
}

var iterable = g();

console.log(iterable.next()) // { value: 1, done: false }
console.log(iterable.return('foo')) // { value: 'foo', done: true }
console.log(iterable.next()) // { value: undefined, done: true }
复制代码

return 会被 finally 捕获:

const g = function* g() {
  yield 1;
  try {
    yield 2;
    yield 3;
  } finally {
    yield 4;
    yield 5;
  }
  yield 6;
}
const iterable = g();
iterable.next() // { value: 1, done: false }
iterable.next() // { value: 2, done: false }
iterable.return(7) // { value: 4, done: false }
iterable.next() // { value: 5, done: false }
iterable.next() // { value: 7, done: true }
复制代码

Generator函数被称为“半协程”(semi-coroutine),只有Generator函数的调用者,才能将程序的执行权还给Generator函数。

执行过程

  • 第一次 next 的执行,代码运行到第一个第一个 yield 之前,返回 yield 之后的值/表达式
  • 第二次执行 next ,传入的参数将替换第一个 yield 位置的语句,并向后执行,直到下一个 yield ,返回值(因此第一次 next 传入的值是被抛弃的;同理 throw 之前也需要至少执行过一次 next
  • 按照第二次起的逻辑,继续重复执行,直到 return 或不再 yield

在执行 yield 之后,Generator等待外部调用 next 再执行之后的代码,在这个过程中,可以执行一些异步操作,再调用 next 将结果返回Generator同时将程序的执行权还给Generator并继续执行,这也是可以用Generator模拟类似async/await的同步写法的原因。

generator与异步

因此,我们可以进行这样的操作:

const getUserId = (token) => {
  setTimeout(() => {
    iterable.next(parseInt(token.split(' ')[1], 10));
  }, 0);
};

const getUserInfo = (userId) => {
  setTimeout(() => {
    iterable.next({ userId });
  }, 0);
};

const g = function* (token) {
  const userId = yield getUserId(token);
  console.log(userId);
  const data = yield getUserInfo(userId);
  console.log(data);
  return data;
};

const iterable = g('Bearer 123');
iterable.next();
复制代码

还可以使用 co 库:

const co = require('co');

const getUserId = (token) => {
  return Promise.resolve(parseInt(token.split(' ')[1], 10));
};

const getUserInfo = (userId) => {
  return Promise.resolve({ userId });
};

const fn = co.wrap(function* (token) {
  const userId = yield getUserId(token);
  const data = yield getUserInfo(userId);
  return data;
});

fn('Bearer 123').then(data => {
  console.log(data);
});
复制代码

自己动手实现 co

这里我们实现一个简化版的 co 库,我们所要做的,是实现如下过程的自动化:

  • 调用Generator的 next 方法
  • next 返回的 Promise 完成时,用Promise的返回值再次调用 next 方法
  • 反复执行第二步,直到 next 返回的 donetrue

以下是实现代码:

co 函数将返回一个Promise:

const co = function (fn, ...args) {
  return new Promise((resolve, reject) => {
  	...
  });
};
复制代码

首先我们调用传入的 fn 来生成带有 next 方法的 generator 实例:

gen = fn.apply(this, args);
复制代码

我们需要实现 next 函数:在调用 gen.next 方法之后,我们会获得带有 valuedone 的结果,这个结果作为参数调用 next ;如果 donetrue ,则直接 resolve(value) ;否则, value 应该是一个 Promise 实例,我们通过 value.then 注册回调。

value.then 的回调中,我们将使用(Generator外部的异步或同步方法的)返回的值继续调用 gen.next ,并用其返回的结果继续调用 next 进行处理:

const onFulfilled = function (res) {
  let ret;
  try {
    ret = gen.next(res);
  } catch (e) {
    return reject(e);
  }
  next(ret);
};
const next = function (p) {
  const { done, value } = p;
  if (done) {
    resolve(value);
  } else {
    (value.then ? value : Promise.resolve(value)).then(onFulfilled, reject);
  }
};
复制代码

最后,别忘了调用一次 gen.next 来启动整个过程:

onFulfilled();
复制代码

这里判断 value.then 并使用 Promise.resolve ,是为了兼容外部调用可以是同步方法的情况,例如:

const getUserId = (token) => {
  return parseInt(token.split(' ')[1], 10);
};
复制代码

最后我们实现 co.wrap 函数,这里进行了柯里化:

co.wrap = function (fn) {
  return function(...args) {
    return co.call(this, fn, ...args);
  };
};
复制代码

完整示例代码

const co = function (fn, ...args) {
  return new Promise((resolve, reject) => {
    gen = fn.apply(this, args);
    const onFulfilled = function (res) {
      let ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    };
    const next = function (p) {
      const { done, value } = p;
      if (done) {
        resolve(value);
      } else {
        (value.then ? value : Promise.resolve(value)).then(onFulfilled, reject);
      }
    };
    onFulfilled();
  });
};
co.wrap = function (fn) {
  return function(...args) {
    return co.call(this, fn, ...args);
  };
};

// demo
const getUserId = (token) => {
  return parseInt(token.split(' ')[1], 10);
};

const getUserInfo = (userId) => {
  return Promise.resolve({ userId });
};

const fn = co.wrap(function* (token) {
  const userId = yield getUserId(token);
  const data = yield getUserInfo(userId);
  return data;
});

fn('Bearer 123').then(data => {
  console.log(data);
});
复制代码

参考


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Programming Ruby

Programming Ruby

Dave Thomas、Chad Fowler、Andy Hunt / Pragmatic Bookshelf / 2004-10-8 / USD 44.95

Ruby is an increasingly popular, fully object-oriented dynamic programming language, hailed by many practitioners as the finest and most useful language available today. When Ruby first burst onto the......一起来看看 《Programming Ruby》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器