异步操作 generator 函数

140 阅读4分钟

基本概念

Generator 字面上可以译为“生成器”, MDN上介绍生产器对象是由generator function返回的的遍历器对象 这个对象封装了多个内部状态。 所以理解上可以认为

语法上Generator函数是一个内部封装了多个状态的状态机。执行Generator函数可以返回一个可依次遍历函数内容状态的遍历器对象

什么是状态机

(注: 状态机 即有限状态自动机的简称,是一种对现实中事物运行规则的抽象的数学模型(这个模型的4大特点:state\event\action\transition)。比如说一个自动开关门可以抽象成 具有: open和closed两个state状态. 对于自动门的触发条件/指令 譬如 “按下开门按钮”抽象为事件event, 事件执行后要执行的动作action “按下开门按钮”事件发生后的执行动作 “开门”, 以及状态的变化 如 “开门过程” 这个transition)

语法行为

Generator函数和普通函数上有很大不同:

function* generatorFun(){
    yifunction* gen() {
  yield 1;
  yield 2;
  yield 3;
}

let g = gen();
// "Generator { }"

1、声明写法上在function 关键字和函数名之间有一个 星号(*)

2、函数体内使用yield 表达式来定义不同状态

3、函数体内使用 return 定义结束状态(如果没有写return 最后一个状态将会被定义为 ‘undefined’)

4、调用Generator函数会返回一个遍历器(生产器)对象,这个返回的对象又提供了一个next()方法.调用一次next方法就会切换一次状态(这个next的返回值是一个带value和done属性的对象 value代表状态done代表遍历完了)

function* gen(){
    yield 1
    yield 2
    return  3
}
const res = gen()
console.log('res', res.next()) // 调用1次  将状态切到1  { value: 1, done: false }
console.log('res', res.next()) // 调用第二次  将状态切到2  { value: 2, done: false }
console.log('res', res.next()) // 调用第三次  将状态切到3  { value: 3, done: true } 无return 将会会返回 { value: undefined, done: true }

所以从状态机的角度可以看到 generator函数 使用了 yield 表达式和 return 抽象出了状态机的状态、使用next()方法用来转变状态。

yield表达式和next方法

function* gen(){
    yield 1
    console.log('11')
    yield 2
    console.log('22')
}

const res = gen()
console.log('res', res.next()) // 调用1次  将状态切到1  { value: 1, done: false }
// 调用第二次 输出 ‘11’、 将状态切到2  返回{ value: 2, done: false } 。后面的 console.log('22')不会运行 因为已经被‘2’前的yield‘暂停’了
console.log('res', res.next()) 

总结:调用Generator函数返回的遍历对象由next方法去遍历切换状态遇到yield表达式停止 并将 紧跟在yield表达式的值作为返回对象的value属性值也即状态

yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

function* gen(){
    let num = yield 1
    console.log('res', num)
    yield 2 + num
    return 3
}
const res = gen()
console.log(res.next()) // 第一次调用 next 返回对象的状态value值是 yield 后的1 即 { value: 1, done: false }
console.log(res.next(3)) // { value: 5, done: false } 第二次调用 next 返回对象的状态value值是 yield后的 2 + num  即 这里传入了参数3指定了上一次yield的接过 3  即num = 3 

其它generator对象的方法return、throw。

return方法

generator提供的return 方法也和next一样是用来切换状态的只不过是直接切到结束状态 所带参数为指定结束状态的状态value值 返回值也和next一样 但done属性会置为true

function* gen(){
    yield 1
    yield 2
    return 3
}
const res = gen()
console.log(res.next())  // { value: 1, done: false }
console.log(res.return(4)) // { value: 4, done: true } 直接切换到结束状态 指定状态value值为4、同时已经结束 done为 true
console.log(res.next()) // { value: undefined, done: true } 因为上一步已经将状态都切换结束  done :true  想切下一个状态 无对应的 所以是 value: undefined
console.log(res.return(5)) // { value: 5, done: true } 这里切换到结束状态并且重新指定值 为 5 
console.log(res.return()) // { value: undefined, done: true } 这里切换到结束状态且无指定状态

throw方法

mdn:用来向生成器抛出异常,并恢复生成器的执行,返回带有 done 及 value 两个属性的对象。

function* gen(){
    yield 1
    yield 2
    return 3
}
try{
  let result = gen()
  result.next()
  tt = result.throw('this is a error') // {value: 1, done} 当前返回值还是为上一次next()结果的 value:1 因为调用throw抛出了异常
}catch(error){
   console.log("error:", error) // error: this is a error
}

总结:next()、throw()、return()这三个方法本质上都是在让 Generator 函数恢复执行(切换状态),并且使用不同的语句替换yield表达式。

next()是将yield表达式替换成一个值。

const g = function* (x, y) {
  let result = yield x + y;
  return result;
};

const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}

gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;

return()是将yield表达式替换成一个return语句

gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;

throw()是将yield表达式替换成一个throw语句。

gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));