改造上节案例

  1. 通过 Generator 函数 和 yield 让异步代码看起来像同步代码一样执行;但是这样里面存在的一个问题就是生成器函数直接执行,需要手动处理;为了解决深层回调的问题借助了 co 库去执行生成器函数,从而解决了回调地狱的问题;

    // name.txt 的内容 age.txt
    // age.txt  的内容 10
    const fs = require('fs').promises;
    
    // 代码编写更像是同步的,更友好的编码方式 (执行还是异步的)
    function* read() { 
        try {
            let name = yield fs.readFile('name.txt', 'utf8');
            let age = yield fs.readFile(name, 'utf8');
            return age;
        } catch (error) {
            console.log(error);
        }
    }
    
    // 手写 co,异步迭代:co 有现成的库,koa、express 的作者 tj
    const co = it => {
        //  需要返回一个 new Promise() 实例
        return new Promise((resolve, reject) => {
            // 异步迭代的核心是 => 回调函数
            function next(data) {
                let { value, done } = it.next(data);
                if (!done) {
                    // 默认成功后会调用 next 方法,将结果传递到 next 函数中
                    // 如果 yield 返回的不是一个 Promise 对象时,我们对 value 使用了 Promise.resolve() 进行了包装,这样就可以处理返回一个普通值时没有 then 方法的问题
                    Promise.resolve(value).then(next, reject);
                } else {
                    resolve(value);
                }
            }
            next();
        });
    }
    
    // 测试:co 会将生成器的结果转成 promise
    co(read())
        .then(data => console.log(data))
        .catch(err => console.log(err))
    
  2. 其实最希望的是能像执行普通函数一样直接调用 getValue 就能执行并得到结果;async/await 的出现就是为了抹平在调用时所做的额外步骤;

    // name.txt 的内容 age.txt
    // age.txt  的内容 10
    const fs = require('fs').promises;
    
    async function getValue() {
      try {
        let name = await fs.readFile('name.txt', 'utf8');
        let age = await fs.readFile(name, 'utf8');
        return age;
      } catch (error) {
        console.log(error);
      }
    }
    
    getValue();	// 控制台打印的值是:10
    

async、await 用法

  1. 定义一个异步函数时需要使用 asyncfunction 关键字一起来完成,类似生成器函数中的 yield 来暂停异步任务,在 async 函数中使用 await 关键去等待异步任务返回的结果;

  2. async 函数其本质是 Promise + Generator 函数组成的语法糖,它为了减少了 Promise 的链式调用,解放了 Generator 函数的单步执行;

async

  1. async 函数返回的是一个 promise 对象,return 的值就是 promise 实例的值,最主要的作用是配合 await 使用的,一旦在函数中使用 await,那么当前的函数必须用 async 修饰;

  2. 示例代码:

    JavaScript
    JavaScript
    async function fn() {
      return 10;
    }
    console.log(fn()); // Promise { 10 }
    
    async function fn() {
      // 因为加入了 setTimeout 异步操作,直接向下执行,没有 return,fn 的值为 undefined
      setTimeout(_ => {
        return 10;
      }, 1000);
    }
    console.log(fn()); // Promise { undefined }
    

await

  1. await 只能处理 promisefulfilled (成功) 的时候,await 是异步编程,当代码执行到此行,构建一个异步的微任务,等待 promise 返回结果,否则 await 下面的代码都不能执行 (await 下面的代码也都被列到任务队列中,相当于 promise.then)

    1. promise 返回 rejected 失败 状态,则 await 不会接收其返回结果,await 下面的代码也不会再继续执行;
    2. promise 返回 fulfilled 成功 状态,则继续执行下面的代码;
  2. 示例代码:

    JavaScript
    JavaScript
    JavaScript
    async function fn() {
      console.log(1);
      let AA = await Promise.resolve(100);
      console.log(AA);
    }
    fn();
    console.log(2);
    // 1 2 100
    
    let p1 = Promise.resolve(100);
    let p2 = new Promise(resolve => {
      setTimeout(_ => {
        resolve(200);
      }, 1000);
    });
    
    async function fn() {
      console.log(1);
      // await 以下的所有代码都不执行,放入事件队列微任务中,1s 后返回输出 200
      let result = await p2;
      console.log(result);
      // await 以下的所有代码都不执行,放入事件队列微任务中,p1 执行完后返回输出 100
      let AA = await p1;
      console.log(AA);
    }
    fn();
    console.log(2);
    // 1  2  200  100
    
    async function fn() {
      let reason = await Promise.reject(3);
      console.log(reason);
    }
    fn();
    // 报错:Uncaught (in promise) 3
    

错误捕获

  1. 使用 try…catch 捕获错误,多个任务时无法细粒度的定位出问题 (try…catch 只能捕获同步的错误)

    const task = function (num) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (num === 300) {
            reject('throw error')
          } else {
            resolve('hello'); 
          }
        }, 1000)
      })
    }
    
    // 如果这样写,判断不出来哪一个 task 报错了,不能进行细粒度的定位
    async function foo() {
      try {
        let res1 = await task(100);
        let res2 = await task(200);
        let res3 = await task(300);
      } catch(e) {
        console.log(e);
      }
    }
    foo()	// throw error
    
  2. 使用 catch (最佳实践)

    const task = function (num) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (num === 300) {
            reject('throw error')
          } else {
            resolve('hello'); 
          }
        }, 1000)
      })
    }
    
    async function foo() {
      let res1 = await task(100).catch(err => console.log('res1', err));
      let res2 = await task(200).catch(err => console.log('res2', err));
      let res3 = await task(300).catch(err => console.log('res3', err));
    }
    foo()	// res3 throw error
    

滥用 async/await

  1. 其实这里是一个坑,很多时候 async/await 都会被滥用导致程序卡顿,执行时间过长;

    // 如果后一个任务依赖前一个任务这样写完全没问题,但是如果是三个独立的异步任务,那这样写就会导致程序执行时间加长
    async function foo() {
      let res1 = await task(100);
      let res2 = await task(200);
      let res3 = await task(300);
    
      return { res1, res2, res3 }
    }
    
    foo()
    
  2. 针对没有关联的异步任务需要把它们解开;

    async function foo() {
      let res1Promes = task(100);
      let res2Promes = task(200);
      let res3Promes = task(300);
    
      let res1 = await res1Promes;
      let res2 = await res2Promes;
      let res3 = await res3Promes;
    
      console.log({ res1, res2, res3 })
    
      return { res1, res2, res3 }
    }
    foo();	// { res1: 'hello 100', res2: 'hello 200', res3: 'hello 300' }
    

面试题

第 1 题(头条、字节)

  1. 题目

    async function async1() {
      console.log('async1 start');
    
      await async2();
      console.log('async1 end');
    
      // 浏览器环境:识别 await 后面跟的是 promise 的话默认就会直接调用 promise.then
      // 等价于 async2().then(() => console.log('async1 end'))
    
      // node 低版本环境:node 识别不出 await,则直接用 promsie 的 resolve 包裹起来,resole 里面是 promise 会调用 then,然后再调用 then(() => console.log('async1 end')),相当于调了两次 then,所以结果和浏览器的不太一致
      // new Promise((resolve, reject) => resolve(async2())).then(() => console.log('async1 end'));
    }
    
    async function async2() {
      console.log('async2');
    }
    
    console.log('script start');
    
    setTimeout(function () {
      console.log('setTimeout');
    }, 0)
    
    async1();
    
    new Promise(function (resolve) {
      console.log('promise1');
      resolve();
    }).then(function () {
      console.log('promise2');
    });
    
    console.log('script end');
    
    // 浏览器环境执行:
    //  script start
    //  async1 start
    //  async2
    //  promise1
    //  script end
    //  async1 end
    //  promise2
    //  setTimeout
    
  2. 图解

第 2 题(字节)

  1. 题目

    console.log(1);
    
    setTimeout(_ => { console.log(2); }, 1000);
    
    async function fn() {
      console.log(3);
      setTimeout(_ => { console.log(4); }, 20);
      return Promise.reject();
    }
    
    async function run() {
      console.log(5);
      await fn();
      console.log(6);
    }
    
    run();
    
    // 需要执行 150MS 左右
    for (let i = 0; i < 90000000; i++) { }
    
    setTimeout(_ => {
      console.log(7);
      new Promise(resolve => {
        console.log(8);
        resolve();
      }).then(_ => { console.log(9); });
    }, 0);
    
    console.log(10);
    
    // 1
    // 5
    // 3
    // 10
    // 4
    // 7
    // 8
    // 9
    // 2
    
  2. 图解

第 3 题

async function m1() {
  return 1;
}

async function m2() {
  const n = await m1();
  console.log(n);
  return 2;
}

async function m3() {
  const n = m2(); // 没有 await,返回值是 Promise<pending>
  console.log(n);
  return 3;
}

m3().then((n) => {
  console.log(n);
});

m3();

console.log(4);
// Promise { <pending> }
// Promise { <pending> }
// 4
// 1
// 3
// 1

第 4 题

var a;
var b = new Promise((resolve, reject) => {
  console.log('promise1');
  setTimeout(() => {
    resolve();
  }, 1000);
})
  .then(() => {
    console.log('promise2');
  })
  .then(() => {
    console.log('promise3');
  })
  .then(() => {
    console.log('promise4');
  });

a = new Promise(async (resolve, reject) => {
  console.log(a);
  await b;
  console.log(a);
  console.log('after1');
  await a;
  resolve(true);
  console.log('after2');
});

console.log('end');
// promise1
// undefined
// end
// promise2
// promise3
// promise4
// Promise { <pending> }
// after1
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 改造上节案例
  2. 2. async、await 用法
    1. 2.1. async
    2. 2.2. await
    3. 2.3. 错误捕获
    4. 2.4. 滥用 async/await
  3. 3. 面试题
    1. 3.1. 第 1 题(头条、字节)
    2. 3.2. 第 2 题(字节)
    3. 3.3. 第 3 题
    4. 3.4. 第 4 题