• 大厂面试必备 - async/await 详解


     async作用

    async声明function是一个异步函数,返回一个promise对象,可以使用 then 方法添加回调函数。async函数内部return语句返回的值,会成为then方法回调函数的参数。

    1. async function test() {
    2. return 'test';
    3. }
    4. console.log(test); // [AsyncFunction: test] async函数是[`AsyncFunction`]构造函数的实例
    5. console.log(test()); // Promise { 'test' }
    6. // async返回的是一个promise对象
    7. test().then(res=>{
    8. console.log(res); // test
    9. })
    10. // 如果async函数没有返回值 async函数返回一个undefined的promise对象
    11. async function fn() {
    12. console.log('没有返回');
    13. }
    14. console.log(fn()); // Promise { undefined }
    15. // 可以看到async函数返回值和Promise.resolve()一样,将返回值包装成promise对象,如果没有返回值就返回undefined的promise对象

    await

    await 操作符只能在异步函数 async function 内部使用。如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果,也就是说它会阻塞后面的代码,等待 Promise 对象结果。如果等待的不是 Promise 对象,则返回该值本身。

    1. async function test() {
    2. return new Promise((resolve)=>{
    3. setTimeout(() => {
    4. resolve('test 1000');
    5. }, 1000);
    6. })
    7. }
    8. function fn() {
    9. return 'fn';
    10. }
    11. async function next() {
    12. let res0 = await fn(),
    13. res1 = await test(),
    14. res2 = await fn();
    15. console.log(res0);
    16. console.log(res1);
    17. console.log(res2);
    18. }
    19. next(); // 1s 后才打印出结果 为什么呢 就是因为 res1在等待promise的结果 阻塞了后面代码。

    错误处理

    如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject

    1. async function test() {
    2. await Promise.reject('错误了')
    3. };
    4. test().then(res=>{
    5. console.log('success',res);
    6. },err=>{
    7. console.log('err ',err);
    8. })
    9. // err 错误了

    防止出错的方法,也是将其放在try...catch代码块之中。

    1. async function test() {
    2. try {
    3. await new Promise(function (resolve, reject) {
    4. throw new Error('错误了');
    5. });
    6. } catch(e) {
    7. console.log('err', e)
    8. }
    9. return await('成功了');
    10. }

    多个await命令后面的异步操作,如果不存在继发关系(即互不依赖),最好让它们同时触发。

    1. let foo = await getFoo();
    2. let bar = await getBar();
    3. // 上面这样写法 getFoo完成以后,才会执行getBar
    4. // 同时触发写法 ↓
    5. // 写法一
    6. let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    7. // 写法二
    8. let fooPromise = getFoo();
    9. let barPromise = getBar();
    10. let foo = await fooPromise;
    11. let bar = await barPromise;

    async/await 优雅的错误处理方法

    1. /**
    2. * @description ### Returns Go / Lua like responses(data, err)
    3. * when used with await
    4. *
    5. * - Example response [ data, undefined ]
    6. * - Example response [ undefined, Error ]
    7. *
    8. *
    9. * When used with Promise.all([req1, req2, req3])
    10. * - Example response [ [data1, data2, data3], undefined ]
    11. * - Example response [ undefined, Error ]
    12. *
    13. *
    14. * When used with Promise.race([req1, req2, req3])
    15. * - Example response [ data, undefined ]
    16. * - Example response [ undefined, Error ]
    17. *
    18. * @param {Promise} promise
    19. * @returns {Promise} [ data, undefined ]
    20. * @returns {Promise} [ undefined, Error ]
    21. */
    22. const handle = (promise) => {
    23. return promise
    24. .then(data => ([data, undefined]))
    25. .catch(error => Promise.resolve([undefined, error]));
    26. }
    27. async function userProfile() {
    28. let [user, userErr] = await handle(getUser());
    29. if(userErr) throw new Error('Could not fetch user details');
    30. let [friendsOfUser, friendErr] = await handle(
    31. getFriendsOfUser(userId)
    32. );
    33. if(friendErr) throw new Error('Could not fetch user\'s friends');
    34. let [posts, postErr] = await handle(getUsersPosts(userId));
    35. if(postErr) throw new Error('Could not fetch user\'s posts');
    36. showUserProfilePage(user, friendsOfUser, posts);
    37. }

    将对 await 处理的方法抽离成公共的方法,在使用 await 调用 awaitWrap 这样的方法是不是更优雅了呢。如果使用 typescript 实现大概是这个样子

    1. function awaitWrap(promise: Promise): Promise<[U | null, T | null]> {
    2. return promise
    3. .then<[null, T]>((data: T) => [null, data])
    4. .catch<[U, null]>(err => [err, null])
    5. }

    async/await优点

    async/await的优势在于处理由多个Promise组成的 then 链,在之前的Promise文章中提过用then处理回调地狱的问题,async/await相当于对promise的进一步优化。 假设一个业务,分多个步骤,且每个步骤都是异步的,而且依赖上个步骤的执行结果。

    1. // 假设表单提交前要通过俩个校验接口
    2. async function check(ms) { // 模仿异步
    3. return new Promise((resolve)=>{
    4. setTimeout(() => {
    5. resolve(`check ${ms}`);
    6. }, ms);
    7. })
    8. }
    9. function check1() {
    10. console.log('check1');
    11. return check(1000);
    12. }
    13. function check2() {
    14. console.log('check2');
    15. return check(2000);
    16. }
    17. // -------------promise------------
    18. function submit() {
    19. console.log('submit');
    20. // 经过俩个校验 多级关联 promise传值嵌套较深
    21. check1().then(res1=>{
    22. check2(res1).then(res2=>{
    23. /*
    24. * 提交请求
    25. */
    26. })
    27. })
    28. }
    29. submit();
    30. // -------------async/await-----------
    31. async function asyncAwaitSubmit() {
    32. let res1 = await check1(),
    33. res2 = await check2(res1);
    34. console.log(res1, res2);
    35. /*
    36. * 提交请求
    37. */
    38. }

    原理

    async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

    1. async function fn(args) {
    2. // ...
    3. }
    4. // 等同于
    5. function fn(args) {
    6. return spawn(function* () {
    7. // ...
    8. });
    9. }
    1. /*
    2. * Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。
    3. * 异步操作需要暂停的地方,都用 yield 语句注明
    4. * 调用 Generator 函数,返回的是指针对象(这是它和普通函数的不同之处),。调用指针对象的 next 方法,会移动内部指针。
    5. * next 方法的作用是分阶段执行 Generator 函数。每次调用 next 方法,会返回一个对象,表示当前阶段的信息( value 属性和 done 属性)。
    6. * value 属性是 yield 语句后面表达式的值,表示当前阶段的值;
    7. * done 属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。
    8. */
    9. // 了解generator的用法
    10. function* Generator() {
    11. yield '1';
    12. yield Promise.resolve(2);
    13. return 'ending';
    14. }
    15. var gen = Generator(); // 返回指针对象 Object [Generator] {}
    16. let res1 = gen.next();
    17. console.log(res1); // 返回当前阶段的值 { value: '1', done: false }
    18. let res2 = gen.next();
    19. console.log(res2); // 返回当前阶段的值 { value: Promise { 2 }, done: false }
    20. res2.value.then(res=>{
    21. console.log(res); // 2
    22. })
    23. let res3 = gen.next();
    24. console.log(res3); // { value: 'ending', done: true }
    25. let res4 = gen.next();
    26. console.log(res4); // { value: undefined, done: true }

    Generator实现async函数

    1. // 接受一个Generator函数作为参数
    2. function spawn(genF) {
    3. // 返回一个函数
    4. return function() {
    5. // 生成指针对象
    6. const gen = genF.apply(this, arguments);
    7. // 返回一个promise
    8. return new Promise((resolve, reject) => {
    9. // key有next和throw两种取值,分别对应了gen的next和throw方法
    10. // arg参数则是用来把promise resolve出来的值交给下一个yield
    11. function step(key, arg) {
    12. let result;
    13. // 监控到错误 就把promise给reject掉 外部通过.catch可以获取到错误
    14. try {
    15. result = gen[key](arg)
    16. } catch (error) {
    17. return reject(error)
    18. }
    19. // gen.next() 返回 { value, done } 的结构
    20. const { value, done } = result;
    21. if (done) {
    22. // 如果已经完成了 就直接resolve这个promise
    23. return resolve(value)
    24. } else {
    25. // 除了最后结束的时候外,每次调用gen.next()
    26. return Promise.resolve(
    27. // 这个value对应的是yield后面的promise
    28. value
    29. ).then((val)=>step("next", val),(err) =>step("throw", err))
    30. }
    31. }
    32. step("next")
    33. })
    34. }
    35. }

    测试

    1. function fn(nums) {
    2. return new Promise(resolve => {
    3. setTimeout(() => {
    4. resolve(nums)
    5. }, 1000)
    6. })
    7. }
    8. // async 函数
    9. async function testAsync() {
    10. let res1 = await fn(1);
    11. console.log(res1); // 1
    12. let res2 = await fn(2);
    13. console.log(res2); // 2
    14. return res2;
    15. }
    16. let _res = testAsync();
    17. console.log('testAsync-res',_res); // Promise
    18. _res.then(v=>console.log('testAsync-res',v)) // 2
    19. // Generator函数
    20. function* gen() {
    21. let res1 = yield fn(3);
    22. console.log(res1); // 3
    23. let res2 = yield fn(4);
    24. console.log(res2); // 4
    25. // let res3 = yield Promise.reject(5);
    26. // console.log(res3);
    27. return res2;
    28. }
    29. let _res2 = spawn(gen)();
    30. console.log('gen-res',_res2); // Promise
    31. _res2
    32. .then(v=>console.log('gen-res',v)) // 4
    33. .catch(err=>{console.log(err)}) // res3 执行会抛出异常

    总结

    async/await语法糖可以让异步代码变得更清晰,可读性更高,所以快快卷起来吧。Generator有兴趣的可以了解一下。

  • 相关阅读:
    Adobe Illustrator黑洞文字
    cesium火箭发射,模型控制,模型动画,模型移动
    Vue-cli前端工程配置
    javaSE和javaEE区别
    MySQL补充开窗函数
    C++递归函数
    C# 给某个方法设定执行超时时间
    JavaScript入门④-万物皆对象:Object
    【智能家居】东胜物联ODM定制ZigBee网关,助力能源管理解决方案商,提升市场占有率
    Spring cloud负载均衡@LoadBalanced & LoadBalancerClient
  • 原文地址:https://blog.csdn.net/qq_38261819/article/details/126876729