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

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

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

使用 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 the Mobile Web

Programming the Mobile Web

Maximiliano Firtman / O'Reilly Media / 2010-07-23 / $44.99

* Learn how to use your existing skills to move into mobile web development * Discover the new possibilities of mobile web development, and understand its limitations * Get detailed coverage of ......一起来看看 《Programming the Mobile Web》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

SHA 加密
SHA 加密

SHA 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具