async、 await、yield * 、 function*解析

1,674 阅读6分钟

最近做了一些面试题,

const myPromise = Promise.resolve('Woah some coll data');
(async () => {
    try{
        console.log(await myPromise);
    } catch {
        throw new Error(`Oops did't work`);
    } finally {
        console.log('Oh finally!');
    }
})();
输出:
Woah Some coll data Oh finally
function* startGame() {
    const answer = yield 'Do you love JavaScript?';
    if (answer !== 'yes'){
        return "Oh wow... Guess we're gone here";
    }
    return 'JavaScript loves you back';
}
const game = startGame();

console.log(); // 如何输出 Do you love JavaScript? 
console.log(); // 如何输出 JavaScript loves you back;

A game.next('yes').value and game.next().value
B game.next.value('yes') and game.next.value()
C game.next().value and game.next('yes').value
D game.next.value() and game.next.value('yes')

看到async 和 await 等一些知识点,突然感觉好像不是对这两个关键字特别熟悉,所以就专门写一个文档记录下自己的学习。

1. async function

用来定义一个返回AsyncFunction 对象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其结果。

返回的Promise 对象会运行执行(resolve)异步函数的返回结果,或者运行拒绝(reject)-如果异步函数抛出异常的话。

2. await 操作符

await 操作符用于等待一个Promise对象。他只能在异步函数async Function 中使用。

语法: [return_value] = await expression;

返回值:
返回 Promise 对象的处理结果。如果表达式不是Promise,则返回该值本身。

注意: await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成,然后继续执行下面的程序。

若Promise 正常处理(fulfilled),其 回调的resolve函数参数作为await表达式的值,继续执行 async function .

若Promise 处理异常(rejected),await表达式会把Promise 的异常原因抛出。

function fn2(x){
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(x)''
        }, 2000)
    });
}
async function f1(){
    let x = await 2; // 2
    let y = await fn2(10); // y = 10
}

3. yield

键字用来暂停和恢复一个生成器函数((function* 或遗留的生成器函数)。

[rv] = yield [expression]

expression 定义通过 迭代器协议从生成器函数返回的值。如果省略,则返回undefined rv 返回传递给生成器的next() 方法的可选值,以恢复其执行。

描述

yield 关键字使生成器函数执行暂停,yield 关键字后面的表达式的值返回给生成器的调用者。
yield 关键字时间返回一个 IteratorResult 对象, 它有两个属性 value 和 done, value 属性是对 yield表达式求值的结果,而done 是false, 表示生成器函数尚未完全完成。

一旦遇到 yield 表达式,生成器的代码将会被暂停执行,直到生成器的 next() 方法被调用。每次调用生成器 next() 方法时,生成器都会恢复执行,直到达到以下某个值:

  • yield,导致生成器再次暂停并返回生成器的新值。下一次调用next()时,在yield之后紧接着的语句继续执行。
  • throw用于从生成器中抛出异常。这让生成器完全停止执行,并在调用者中继续执行,正如通常情况下抛出异常一样。
  • 到达生成器函数的结尾;在这种情况下,生成器的执行结束,并且IteratorResult给调用者返回undefined并且done为true。
  • 到达return 语句。在这种情况下,生成器的执行结束,并将IteratorResult返回给调用者,其值是由return语句指定的,并且done 为true。
function* countAppleSales () {
  var saleList = [3, 7, 5];
  for (var i = 0; i < saleList.length; i++) {
    yield saleList[i];
  }
}

let appleStore = countAppleSales(); // Generator { }

4. function*

functino* 这种生命方式(function关键字后跟一个星号)会定义一个生成器函数,它返回一个 Generator 对象。

function* generator(i) {
  yield i;
  yield i + 10;
}

const gen = generator(10);

console.log(gen.next().value);
// expected output: 10

console.log(gen.next().value);
// expected output: 20

描述

生成器函数在执行时能暂停,后面又能从暂停处继续执行。

调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的迭代器对象。当这个迭代器的 next() 方法被首次调用时,其内的语句会执行到第一个 出现 yield 的位置为止,yield 后紧跟迭代器要返回的值。

或者如果用的是 yield* ,则表示将执行权移交给另外一个生成器函数(当前生成器暂停执行)。

next() 方法返回一个对象,这个对象包含两个属性: value 和 done , value 属性表示本次 yield 表达式的返回值,done 表示生成器后续是否还有 yield 语句,即生成器函数是否已经执行完毕并返回。
调用 next() 方法时,如果传入了参数,那么这个参数会传给上一条执行的yield语句左边的变量

function *gen(){
    yield 10;
    x=yield 'foo';
    yield x;
}

var gen_obj=gen();
console.log(gen_obj.next());// 执行 yield 10,返回 10
console.log(gen_obj.next());// 执行 yield 'foo',返回 'foo'
console.log(gen_obj.next(100));// 将 100 赋给上一条 yield 'foo' 的左值,即执行 x=100,返回 100
console.log(gen_obj.next());// 执行完毕,value 为 undefined,donetrue

当在生成器函数中显式 return 时,会导致生成器立即变为完成状态,即调用 next() 方法返回的对象的 done 为 true。如果 return 后面跟了一个值,那么这个值会作为当前调用 next() 方法返回的 value 值。

5. yield*

yield*表达式 用于委托给另外一个 generator 或可迭代对象

yield* [[expression]]
** expression ** 返回一个可迭代对象的表达式(例如数组)

描述

yield* 表达式 迭代操作数,并返回它返回的每个值。

yield* 表达式本身的值是当迭代器关闭时返回的值(当done为true,即为关闭)

委托给其他生成器

function* g1() {
    yield 2;
    yield 3;
    yield 4;
}

function* g2() {
    yield 1;
    yield* g1();
    yield 5;
}
let iterator = g2();

委托给其他可迭代对象

除了生成器对象这一种可迭代对象,yield* 还可以 yield 其它任意的可迭代对象,比如说数组、字符串、arguments 对象等等。

function* g3() {
    yield* [1, 2];
    yield* '34';
    yield* arguments;
}

let iterator = g3(5,6);

yield* 表达式的值

yield* 是一个表达式,不是语句,所以它会有自己的值。

function* g4 () {
    yield* [1,2,3];
    return 'foo';
}
let result;
function* g5 () {
    result = yield* g4();
}

let iterator = g5();

题一

yield* function* 代码分析:

function* generatorOne () {
    yield ['a', 'b', 'c'];
}
function* generatorTwo (){
    yield* ['a', 'b', 'c'];
}

let one = generatorOne();
let two = generatorTwo();

console.log(one.next().value);
console.log(two.next().value);

// 输出
["a", "b", "c"]
a

代码解析:

通过yield 关键字,我们在 Generator 函数里执行yield 表达式,通过 yield* 关键字,我们可也在一个 Generator 函数中执行 (yield表达式),另一个 Generator 函数,或可遍历的对象(如数组)。

在函数 generatorOne 中,我们通过 yield 关键字,yield 了一个完整的数组 ['a', 'b', 'c'], 函数one 通过next方法返回的对象的value 值应该为['a', 'b', 'c']。

在函数generatorTwo 中,我们使用了 yield* 关键字,就相当于函数 two 第一个 yield 的值,等价于在迭代器中第一个 yield 的值。数组也是一个迭代器,会对应的迭代数组中的内容,依次迭代'a', 'b','c'。

题二

function* 调用 next() 方法时,如果传入了参数,那么这个参数会传给上一条执行的yield语句左边的变量

function* startGame() {
    const answer = yield 'Do you love JavaScript?';
    if (answer !== 'yes'){
        return "Oh wow... Guess we're gone here";
    }
    return 'JavaScript loves you back';
}
const game = startGame();

console.log(); // 如何输出 Do you love JavaScript? 
console.log(); // 如何输出 JavaScript loves you back;

A game.next('yes').value and game.next().value
B game.next.value('yes') and game.next.value()
C game.next().value and game.next('yes').value
D game.next.value() and game.next.value('yes')

function* 函数遇到yield 关键字会“暂停”其执行。首先我们需要输出“Do you love JavaScript?” , 这可以通过调用 game.next().value来完成,其次,answer 需要和 ‘yes’ 对比,上面有个知识点 ,当function* 调用 next() 方法时,如果传入了参数,那么这个参数会传给上一条执行的yield语句左边的变量, 因此game.next('yes').value 就可以了