Generator函数和async函数


Generator函数

Generator函数是es6提供的一种异步编程解决方案

  • Generator函数是一个状态机,封装了多个内部状态
  • 执行 Generator函数会返回一个遍历器对象,返回的遍历器对象可以依次遍历Generator函数内部的每一个状态

Generator函数与普通函数的区别:

  • function和函数名之间有一个*
  • 函数体内部使用yield表达式
function* hwGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
const hw = hwGenerator();
console.log(hw);

该函数有三个状态:helloworldreturn语句(结束执行)
调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态指针对象,也就是遍历器Iterator

次调用遍历器对象next()Generator函数才开始执行,使得指针指向下一个状态,遇到第一个yield表达式停止
次调用next()Generator函数从上次yield表达式停下的地方,执行到下一个yield表达式,也就是指针指向下一个状态
直到调用next()返回对象的value属性为undefined,以后再调用next()方法,返回的都是undefined

hw.next();
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }

Generator函数是分段执行的,yield表达式是暂停执行的标记,next()方法可以恢复执行


yield表达式

yield表达式是暂停标志
⚠️:

  1. yield后面的表达式,只有当调用next()方法、内部指针指向该语句的时候才会执行
function* sum() {
  yield 123 + 456;
}

上面代码yield后面的表达式123 + 456不会立即求值,只有当指针指向这一句时才会求值

  1. Generator函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数
function* func() {
  console.log("执行了!");
}

函数func()只有在调用next()方法时才会执行,因为它是一个Generator函数

  1. yield表达式只能用在 Generator 函数里面,用在其他地方都会报错

next()的参数

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

function* func() {
  for (let i = 0; true; i++) {
    let reset = yield i;
    if (reset) {
      i = -1;
    }
  }
}
const f = func();
f.next();	// { value: 0, done: false}
f.next();	// { value: 1, done: false}
f.next(true);	// { value: 0, done: false}

上面代码定义了一个可以无限运行的Generator函数func
当调用next()但不传递参数,每次yield表达式的返回值总是undefined
当调用next()传递一个参数true,变量reset就会被赋值为truei被赋值为-1,下一轮循环就会从-1开始递增

⚠️:由于next()方法的参数表示上一个yield表达式的返回值,所以在第一次使用next()方法时,传递参数是无效的
语义上讲,第一个next()方法用来启动遍历器对象,所以不用带参数


for…of 循环

for...of循环可以自动遍历Generator 函数运行时生成的Iterator对象,且此时不需要再调用next()方法

function* func() {
  yield 1;
  yield 2;
  yield 3;
  return 4;
}
for (let item of func()) {
  console.log(item);
}
// 1 2 3
// 一旦next方法的返回对象的done属性为true,for...of循环就会中止

Generator 函数为不具备Iterator接口的对象提供了遍历操作,让它们可以使用for...of循环
加上遍历器接口的一种写法是:将Generator函数加到对象的Symbol.iterator属性上

obj[Symbol.iterator] = generatorFuction;

Generator的应用

Generator的应用给异步编程带来极大的便利,可以让我们的异步代码同步化

例1:

function* main() {
  let res = yield request('https://api-hmugo-web.itheima.net/api/public/v1/goods/detail');
  console.log(res);
  
  // 执行后面的操作
  console.log("数据请求完成,执行后面的操作")
}

const ite = main();
// 启动遍历器对象
ite.next();

function request() {
  $.ajax({
    url,
    method: 'get',
    success(res) {
      ite.next(res);
    }
  })
}

第一次调用next()main()恢复执行,进到request()中,发送ajax请求,成功回调就会再次调用next(),参数res会被作为main()yield表达式的返回值,所以在main()中打印出来的res就是成功回调中拿到的res

例2:

function* loading() {
  loadUI();
  yield showData();
  hideUI();
}
const ite = loading();
// 启动
ite.next();

// 加载loading...页面
function loadUI() {
  console.log("加载loading...页面");
}
// 数据加载完成...(异步操作)
function showData() {
  setTimeout(() => {
    console.log("数据加载完成");
    // 数据加载完成之后,再次调用next(),让loading()函数继续往下执行
    ite.next();
  },1000)
}
// 关闭loading...页面
function hideUI() {
  console.log("关闭loading...页面");
}

很明显如果我们按定义顺序依次调用loadingUI()showData()hideUI()三个函数,执行顺序会是加载loading页面 -> 关闭loading页面 -> 数据加载完成,这不是我们想要的顺序
我们需要Generator函数来帮助实现异步代码同步化
loading()函数中,第一次调用next(),代码会在yield showData()暂停,进入到showData()函数中,里面的数据加载完成之后才会第二次调用next(),让代码继续往下,调用hiedUI()


async 函数

async函数是什么?
它是Generator 函数的语法糖

async 函数将 Generator 函数的星号(*)替换成async,将yield替换成await

async 函数对Generator 函数的改进:

  1. async函数执行。只需要直接用函数名调用,Generator 函数需要调用next()方法
  2. asyncawait比起*yield,更加语义化。 async表示函数里有异步操作,await表示紧跟在其后的表达式需要等待结果
  3. async函数的返回值是Promise对象,这比Generator 函数的返回值是Iterator对象方便多了

返回Promise对象

async函数的返回值为promise对象,该promise对象的结果是由async函数的返回值决定的

async function f() {
  // 1.如果返回值是非promise值,结果就是成功的promise对象
  // return 1;
  
  // 2.如果返回值是promise对象,结果和这个promise对象一致
  // return new Promise((resolve,reject) => {
  //     reject("err");
  // })
  
  // 3.抛出错误,结果就是失败的promise对象
  // throw "出问题了";
}

使用then()

async函数可以使用then()方法添加回调函数
async函数的return语句返回的值,会成为then()方法回调函数参数

async function f() {
  return 'hello world';
}
f().then(value => console.log(value));
// 'hello world'

async函数如果内部抛出错误,抛出的错误可以被catch()方法回调函数接收到,或者被then()方法失败的回调接收

async function f() {
  throw "出问题了";
}
f().then(value => {
  console.log(value);
}, reason => {
  console.log(reason);
})
// 出问题了

返回的Promise对象的状态变化

async 函数返回的 Promise 对象,必须等到内部所有await 命令后面的 Promise 对象执行完,才会发生状态改变除非遇到return语句或者抛出错误
也就是说,只有async函数内部的异步操作执行完,才会执行then()方法指定的回调函数

async function getTitle(url) {
  let response = await fetch(url);
  let html = await response.text();
  return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"

上面代码中,函数getTitle()内部有三个操作:抓取网页、取出文本、匹配页面标题
只有这三个操作全部完成,才会执行then()方法里面的console.log


await表达式

正常情况下:

  • await 后是一个Promise对象,await返回的是promise成功值
  • await 后不是Promise对象,是其他值,直接将此值作为await返回值

另一种情况下:

  • await 后是一个定义了then()方法的对象,此时await会将其等同于Promise对象
async function f() {
  // await后为promise对象,它返回的就是promise对象的成功值
  let res1 = await new Promise((resolve,reject) => {
    resolve('ok');
  })
  console.log(res1);	// ok
  // await后是其他值,它返回的就是那个值
  let res2 = await 20;
  console.log(res2); 	// 20
}

⚠️:await 后的Promise对象若变为reject状态,则reject的参数会被catch()的回调函数接收到,且async函数会中断执行

async function f() {
  await new Promise((resolve, reject) => {
    reject('err');
  })
  await new Promise((resolve, reject) => {	// 不会执行
    resolve('ok');
  }) 
}
  
f().then(value => console.log(value))
.catch(err => console.log(err))	// err

第二个await是不会执行的,因为第一个await中断了async函数

如果我们有时候希望:即使前一个异步操作失败,也不要中断后面的异步操作

第一种方法:

  • 将第一个await放在try...catch里,这样不管第一个异步操作是否成功,第二个await都会执行
async function f() {
  try {
    await new Promise((resolve, reject) => {
      reject('err');
    })
  } catch (e) {}
  return await new Promise((resolve, reject) => {
    resolve('ok');
  })
}

f().then(value => console.log(value))	// ok

第二种方法:

  • await后的Promise对象再接一个catch(),处理可能会出现的错误
async function f() {
  await new Promise((resolve, reject) => {
    reject('err');
  }).catch(err => console.log(err));	// err
  return await new Promise((resolve, reject) => {
    resolve('ok');
  }) 
}

f().then(value => console.log(value))	// ok

错误处理

如果await后面的异步操作出错,那么等同于async函数返回的Promise对象状态变为reject
防止出错的办法,将其放在try...catch代码块中

async function f() {
  try {
    await new Promise((resolve,reject) => {
      throw new Error('出错了');
    });
  } catch(e) {
  }
  return await('hello world');
}

使用await注意点

  1. 最好把await命令放在try...catch代码块中
    因为await命令后面的Promise对象运行结果可能是rejected
  2. 多个await后面的异步操作,如果不存在继发关系,最好让他们同时触发
// getFoo()和getBar()是两个独立的异步操作,被写成继发关系会比较耗时
// 因为只有getFoo()完成后才会执行getBar(),完全可以让它们同时触发
// let foo = await getFoo();
// let bar = await getBar();

// 像下面这样写, getFoo()和getBar()就会同时触发
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
  1. await命令只能用在async函数中,用在普通函数中会报错

async函数的应用

需要实现的是发送一个ajax请求
要用到之前用promise封装好的sendAJAX函数

sendAJAX()会返回一个promise对象,且成功值就是响应体
我们直接将这个函数的调用放在await后边,await返回的就是成功值,也就是响应体
在这里插入图片描述

在这里插入图片描述


部分参考:
https://es6.ruanyifeng.com/?search=filter&x=0&y=0#docs/generator
https://es6.ruanyifeng.com/?search=filter&x=0&y=0#docs/async

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值