hzzzzzzzq / Blog

这是我记录博客的地方,希望能多写一些技术博客,JS、ES、React、Vue等
14 stars 0 forks source link

ES 6 系列 - 10. 三种异步之Generator函数 #11

Open hzzzzzzzq opened 2 years ago

hzzzzzzzq commented 2 years ago

Generator 函数

介绍

GeneratorES 6 提供的一种异步编程解决方案。

// 普通函数
function a() {}

// Generator
function* generator() {
  yield 'hello';
  yield 'hzzzzzzzq';
  return 'good';
}

Generator 函数的调用方式与普通函数是一样的,在函数名后面加上一对圆括号。

yield 表达式

yield 表达式就是 Generator 的暂停标志。

function* generator() {
  yield 'hello'; // 第一个 next 时,执行
  yield 'hzzzzzzzq'; // 第二个 next 时,执行
  return 'good'; // 第三个 next 时,执行
}

在上面的代码中,我们可以来感受上面的用途。

只有在调用 next 方法将指针移到这一句时,才会进行求值。

return 不同的地方在于,return 不具备记忆功能,也就说,在一个函数中,return 只能执行一次,而 yield 具备记忆功能,可以通过 next 方法继续执行代码。

next 方法

next 普通使用

next 方法就是 generator 函数的内置执行器,如果遇到 yield 时,只有通过调用 next,才会继续执行代码。

next 代码的执行值,我们可以打印来看,会是一个对象,带有 valuedone 两个属性。

function* generator() {
  yield 'hello';
  yield 'hzzzzzzzq';
  return 'good';
}

const gen = generator();
console.log(gen.next()); // { value: 'hello', done: false }
console.log(gen.next()); // { value: 'hzzzzzzzq', done: false }
console.log(gen.next()); // { value: 'good', done: true }
console.log(gen.next()); // { value: undefined, done: true }

next 方法参数

next 方法可以带一个参数,该参数会被当作上一个 yield 表达式的值

function* generator() {
  while (true) {
    let value = yield null;
    console.log(value);
  }
}
const gen = generator();
console.log(gen.next()); 
console.log(gen.next('world')); 
// { value: null, done: false }
// world
// { value: 'null', done: false }

当然,这个是会被当作上一个 yield 表达式的值,是在函数内部使用,并不是在 next 返回对象中作为 value 的键值返回。他会替换 generator 函数体内的上一个 yield 值并赋值给我们定义的变量 value

for...of 循环

for...of 循环可以自动遍历 Generator 函数运行时生成的 Interator 对象,不需要手动调用 next 方法。

function* gen() {
  yield 'one';
  yield 'two';
  yield 'three';
  yield 'four';
  return 'five';
}
for (let v of gen()) {
  console.log(v);
}
// one two three four

注意:如果 donetrue,则会被立刻终止,所有最后一句 return 语句不在循环中。

处理 for...of 循环之外,扩展运算符,解构赋值和 Array.from 方法内部调用的,都是遍历器接口。 都可以将 Generator 函数作为返回的 Interator 对象,作为参数。

function* gen() {
  yield 'one';
  yield 'two';
  yield 'three';
  yield 'four';
  return 'five';
}
console.log([...gen()]);
// ['one', 'two', 'three', 'four']

console.log(Array.from(gen()));
// ['one', 'two', 'three', 'four']

let [x, y, z] = gen();
console.log(x, y, z);
// one two three

yield*

yield* 就是 用来在一个 Generator 函数里面执行另一个 Generator 函数。

function* generator1() {
  yield 'hello';
  yield 'hzzzzzzzq';
  yield 'good';
}
function* generator() {
  yield* generator1();
}
const gen = generator();
console.log(gen.next().value); // hello
console.log(gen.next().value); // hzzzzzzzq
console.log(gen.next().value); // good
console.log(gen.next().value); // undefined

我们在 generator 函数中,使用了 yield* ,后面调用 generator1 函数。

然后我们就可以直接使用 next 函数,跟普通的 generator 函数一样使用就可以。

Generator.prototype.throw() / return()

throw()

Generator 函数返回的遍历器对象,都会有个 throw 方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。

function* generator() {
  try {
    yield 1;
  } catch (e) {
    console.log('内部', e);
  }
}
let gen = generator()
gen.next();
try {
  gen.throw('错误1');
  gen.throw('错误2');
} catch (e) {
  console.log('外部', e);
}
// 内部 错误1
// 外部 错误2

在上面的例子中,我们可以看出,第一个 throw 被函数体内部的 catch 语句捕获,然而第二个错误,在内部已经执行过 catch,所有这个错误就被抛出来了,被外面的 try...catch 捕获。

return()

Generator 函数返回的对象中,还有一个 return 方法,可以返回给定的值,并且结束 Generator 函数。

function* generator() {
  yield 'hello';
  yield 'hzzzzzzzq';
  yield 'good';
}
const gen = generator();
gen.next(); // { value: 'hello', done: false }
gen.return('world'); // { value: 'world', done: true }
gen.next(); // { value: undefined, done: true }

从上面的例子,我们可以看出,执行 return 之后,后面的 done 就会变为 true,且传入的参数会作为返回对象的 value 值。

在以后调用时,都会被调整为 undefined,其实就相当于,将 generator 函数中的添加一个 return

Generator 函数的异步操作

我们使用一个 readFile,使用 setTimeout 假装调用接口,然后调用 gen.next() 接口返回对象的 value 属性值进行下一步操作。

const readFile = function (fileName, ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('异步请求成功回调'); // 需要给 `Promise` 一个状态转化,就是异步执行是否成功
    }, ms);
  });
};

function* generator() {
  const str = yield readFile('fileName1', 1000);
  console.log(str);
}
const gen = generator();
const result = gen.next(); // fileName1
result.value.then((val) => {
  console.log(val);
  return val;
}).then((val2) => {
  gen.next('异步请求结果');
});
// 异步请求成功回调
// 异步请求结果

yield 后面用来调用异步函数,然后返回结果输出。