JS学习笔记-async && await原理

875 阅读4分钟
提问 为什么会出现async与await?
如果你有一个这样的场景,b依赖于a,c依赖于b,那么我们只能通过promise then的方式实现。这样的的可读性就会变得很差,而且不利于流程控制,比如我想在某个条件下只走到 b 就不往下执行 c 了,这种时候就变得不是很好控制!
 <script>
        Promise.resolve(data)
            .then(data1 => {
                // do something
            })
            .then(data2 => {
                // do something
            }).catch(err => {
                console.log(err)
            })
            
        // async/await实现上述代码,可读性跟流程控制都变的很方便,但是异常捕获只能通过try/catch来实现
        async () => {
            try {
                const data1 = await Promise.resolve(data);
                // do something
                const resB = await Promise.resolve(data1);
                // do something
            } catch (err) {
                console.log(err)
            }
        }
    </script>

async 和 await 是 ES7 新增两个关键字,它们借鉴了 ES6 中生成器在实际开发中的应用,目的是简化 Promise api 的使用,并非是替代 Promise。

1.async

目的是简化在函数的返回值中对Promise的创建,async 用于修饰函数(无论是函数字面量还是函数表达式),放置在函数最开始的位置,被修饰函数的返回结果一定是 Promise 对象。

 <script>
        async function test1() {
            console.log(1);
            return 2;
        }
        // 等价于
        function test1() {
            return new Promise((resolve, reject) => {
                console.log(1);
                resolve(2);
            })
        }

    </script>

2.await

await关键字必须出现在async函数中!!!await用在某个表达式之前,如果表达式是一个Promise,则得到的是thenable中的状态数据。

 <script>
        async function test1() {
            console.log(1);
            return 2;
        }

        async function test2() {
            const result = await test1();
            console.log(result);
        }

        //等效于


        function test1() {
            return new Promise((resolve, reject) => {
                console.log(1);
                resolve(2);
            })
        }

        function test2() {
            return new Promise((resolve, reject) => {
                test1().then(data => {
                    const result = data;
                    console.log(result);
                    resolve();
                })
            })
        }
         // await 后不是promise
            async function test2() {
                const result = await 1;
                console.log(result);
            }
            //等效于
            async function test2() {
                const result = await Promise.resolve(1);
                console.log(result);
            }

            // 捕获错误
            async function test2() {
                try {
                    const result = await test1();
                    console.log(result);
                } catch (err) {
                    console.log(err)
                }
            }
       
    </script>

3.用async/await处理一开始的问题:

<script>
    // async/await实现上述代码,可读性跟流程控制都变的很方便,但是异常捕获只能通过try/catch来实现
    async () => {
        try {
            const data1 = await Promise.resolve(data);
            // do something
            const resB = await Promise.resolve(data1);
            // do something
        } catch (err) {
            console.log(err)
        }
    }
  </script>
注:如果await的表达式不是Promise,则会将其使用Promise.resolve包装后按照规则运行。且异常捕获只能通过try/catch来实现。

4.生成器(Generator)模拟async与await

生成器 (Generator)

生成器是一个通过构造函数Generator创建的对象,生成器既是一个迭代器,同时又是一个可迭代对象
 <script>
        function* test() {
            console.log("第1次运行")
            yield 1;
            console.log("第2次运行")
            yield 2;
            console.log("第3次运行")
            // return 10;
        }
        test();
        const generator = test();
        console.log(generator)
    </script>
调用next方法

屏幕快照 2021-06-06 下午9.46.25.png

b. 如何创建生成器?
生成器的创建,必须使用生成器函数(Generator Function)
c. 如何书写一个生成器函数呢(加星号*)?
这是一个生成器函数,该函数一定返回一个生成器
functionmethod(){
// ...
 }
 <script>
        const arr1 = [1, 2, 3, 4, 5];
        const arr2 = [6, 7, 8, 9];

        function* createIterator(arr) { // 生成器函数
            for (const item of arr) {
                yield item;
            }
        }

        const iter1 = createIterator(arr1); // 生成器
        const iter2 = createIterator(arr2); // 生成器
        // console.log(iter1);
        // console.log(iter2);

        // 在生成器函数内部,可以调用其他生成器函数,但是要注意加上*号
        function* t1() {
            yield "a"
            yield "b"
        }

        function* test() {
            yield* t1();
            // 相当于
            // yield "a";
            // yield "b";
            yield 1;
            yield 2;
            yield 3;
        }

        const generator = test();
    </script>

5. 生成器函数内部是如何执行的?

1).生成器函数内部是为了给生成器的每次迭代提供的数据

2).每次调用生成器的next方法,将导致生成器函数运行到下一个yield关键字位置

3).yield是一个关键字,该关键字只能在生成器函数内部使用,表示“产生”一个迭代数据。

有哪些需要注意的细节?

1). 生成器函数可以有返回值,返回值出现在第一次done为true时的value属性中

function* test() {
    console.log("第一次运行");
    yield 1;
    console.log("第二次运行");
    yield 2;
    console.log("第三次运行");
    return 10;
}
const generator = test();

3.jpg

2). 调用生成器的next方法时,可以传递参数,传递的参数会交给yield表达式的返回值

3). 第一次调用next方法时,传参没有任何意义

function* test() {
    console.log("start");
    let info = yield 1;
    console.log(info);
    info = yield 2 + info;
    console.log(info);
}
const generator = test();

99aef55d37d622e529fe661ef619f902.png

4). 在生成器函数内部,可以调用其他生成器函数,但是要注意加上*号

6. 生成器的其他API

- return方法:调用该方法,可以提前结束生成器函数,从而提前让整个迭代过程结束

return放在前面

屏幕快照 2021-06-06 下午9.58.42.png

1.jpg

return放在后面

屏幕快照 2021-06-06 下午10.02.22.png

1622988192447.jpg

-throw方法:调用该方法,可以在生成器中产生一个错误

屏幕快照 2021-06-06 下午10.07.01.png

1622988409309.jpg

7.迭代器:

从一个数据集合中按照一定的顺序,不断取出数据的过程

  <script>
        const arr = [1, 2, 3, 4, 5];
        //迭代数组arr
        const iterator = {
            i: 0, //当前的数组下标
            next() {
                var result = {
                    value: arr[this.i],
                    done: this.i >= arr.length
                }
                this.i++;
                return result;
            }
        }

        //让迭代器不断的取出下一个数据,直到没有数据为止
        let data = iterator.next();
        while (!data.done) { //只要没有迭代完成,则取出数据
            console.log(data.value)
            //进行下一次迭代
            data = iterator.next();
        }

        console.log("迭代完成")
    </script>

b. 迭代和遍历的区别?

迭代强调的是依次取数据,并不保证取多少,也不保证把所有的数据取完,遍历强调的是要把整个数据依次全部取出

c. 迭代器与迭代器创建函数

-迭代器(iterator):一个具有next方法的对象,next方法返回下一个数据并且能指示是否迭代完成
- 迭代器创建函数(iterator creator):一个返回迭代器的函数
  <script>
        // 一个简单的迭代器创建函数
        const arr1 = [1, 2, 3, 4, 5];
        const arr2 = [6, 7, 8, 9];

        // 迭代器创建函数  iterator creator
        function createIterator(arr) {
            let i = 0; //当前的数组下标
            return {
                next() {
                    var result = {
                        value: arr[i],
                        done: i >= arr.length
                    }
                    i++;
                    return result;
                }
            }
        }
        // 等效于
        // function* createIterator(arr) {
        //     for (const item of arr) {
        //         yield item;
        //     }
        // }

        const iter1 = createIterator(arr1).next();
        const iter2 = createIterator(arr2);
    </script>

d. 迭代模式(规范):

- 迭代器应该具有得到下一个数据的能力
- 迭代器应该具有判断是否还有后续数据的能力

e. JS规定,如果一个对象具有next方法,并且该方法返回一个对象,该对象的格式如下:

{ value: 值,done: 是否迭代完成 },则认为该对象是一个迭代器

8. next方法:

用于得到下一个数据

- 返回的对象 value:下一个数据的值 - done:boolean,是否迭代完成

可迭代协议:

ES6规定,如果一个对象具有知名符号属性Symbol.iterator,并且属性值是一个迭代器创建函数,则该对象是可迭代的(iterable)。

知名符号:知名符号是一些具有特殊含义的共享符号(根据某个符号名称(符号描述)能够得到同一个符号,Symbol.for("符号名/符号描述")),通过 Symbol 的静态属性得到,该符号用于定义构造函数的静态成员。

我们试想一下,如果生成器能够自动执行所有的迭代任务的话,是否执行下次迭代由 Promise 来决定,那么我们就可以实现 async/await 了?

9.生成器实现async

<script>
        const fn1 = () => new Promise(resolve => setTimeout(() => resolve("data1"), 1000));

        const fn2 = () => new Promise(resolve => setTimeout(() => resolve("data2"), 1000));
      

        function runAsync(generatorFunc) {
            const generator = generatorFunc();
            let result = generator.next(); //启动任务(开始迭代), 得到迭代数据
            handleResult();
            //对result进行处理
            function handleResult() {
                if (result.done) {
                    return; //迭代完成,不处理
                }
                //迭代没有完成,分为两种情况
                   //1. 迭代的数据是一个Promise
                if (result.value && result.value.then && typeof result.value.then === "function") {
                    //1. 迭代的数据是一个Promise
                    //等待Promise完成后,再进行下一次迭代
                    result.value.then(data => {
                        result = generator.next(data)
                        handleResult();
                    })
                } else {
                    //2. 迭代的数据是其他数据,直接进行下一次迭代
                    result = generator.next(result.value)
                    handleResult();
                }
            }
        }
          
        function* test() {
            // await被编译成了yield
            const data1 = yield fn1();
            console.log('data: ', data1);
            const data2 = yield fn2()
            console.log('data2: ', data2);
        }

        runAsync(test);
    </script>

屏幕快照 2021-06-06 下午10.29.59.png

10 总结

1.是一种编写异步代码的新方法。之前异步代码的方案是callback和promise。

2.建立在 promise 的基础上,与promise一样也是非阻塞的。

3.async/await 让异步代码看起来、表现起来更像同步代码。