ES6之async函数的学习及宏任务,微任务的一点分析

Async函数是Generator函数的语法糖

async函数本质上是对generator函数的封装,使其更加有语义化,更加简洁,调用更加方便,其主要优化是如下几点:javascript

  • 内置执行器。即async函数不须要像generator函数同样每次都须要调用.next()方法去执行,它封装了内部的执行器能够帮咱们自动的执行.next()java

  • 更好的语义化。async...await...语法使代码阅读更加有语义化。es6

  • 更广的适用性。co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的promise

    await命令后面,能够是 Promise 对象和原始类型的值(数值、字符串和布尔值,异步

    但这时等同于同步操做)。async

  • 返回值是 Promise。async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象函数

    方便多了。你能够用then方法指定下一步的操做。进一步说,async函数彻底能够看优化

    做多个异步操做,包装成的一个 Promise 对象,而await命令就是内部then命令的动画

    语法糖。this

// async与generator的写法对比
const hello = function (name) {
  console.log(`hello ${name}`);;
};

const say = function* () {
  yield hello('xiaoming');
  yield hello('aiMi');
};
let g = say();
g.next();
g.next();
// 踩了一个坑,generator函数必需要先调用而且赋值给一个变量,才能够next,先调用不会执行,但会返回一个指向内部状态的指针对象(遍历器对象(Iterator Object))
// say().next();
const asyncSay = async function () {
  await hello('xiaoming');
  await hello('aiMi');
};
let result = asyncSay();
console.log(result); // Promise {<pending>}

Async函数基本使用

async函数支持多种声明方式

// 函数声明
async function foo() {}

// 函数表达式
const foo = async function () {};

// 对象的方法
let obj = { async foo() {} };
obj.foo().then(() => {});

// Class 的方法
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jake').then(() => {});

// 箭头函数
const foo = async () => {};

一个简单demo来看下async函数在宏任务与微任务之间的表现

async function asyncTest1() {
    console.log('2');
    await console.log('3');
}
async function asyncTest2() {
    await console.log('4');
    console.log('5');
}
async function asyncTest3() {
    await new Promise(function(resolve) {
               console.log('6');
               resolve();
           }).then(function() {
               console.log('7');
           });
    console.log('8');
}
console.log('1');
setTimeout(function() {
    console.log('9');
    new Promise(function(resolve) {
        console.log('10');
        resolve();
    }).then(function() {
        console.log('11');
    })
    asyncTest1();
});
asyncTest2();
asyncTest3();
new Promise(function(resolve) {
    console.log('12');
    resolve();
}).then(function() {
    console.log('13');
});
// 打印顺序
1==>4==>6==>12==>5==>7==>13==>8==>9==>10==>2==>3==>11

image

借用大神的一张图来理解一下js的执行顺序,一般咱们任务分为同步与异步,通常同步任务进入主线程,异步任务进入event table并完成指定的事情之后注册回调函数进入event queue,当主线程的任务执行完毕之后会从event queue去取异步任务进入主线程。(ps:我的理解这里其实会遵循js单线程的原则,执行的一直是主线程。)这时候任务又会进行细分,分为宏任务与微任务,宏任务与微任务分别存放在不一样的event queue中,当主线程完成之后会先取微任务中的任务,当微任务中的任务执行完之后再执行宏任务队列中的任务,全都完成之后会继续循环次操做(事件轮询)。

结合上述描述分析上面代码,由于以前不太肯定await后边会被分配在什么任务中,执行之后分析发现await后的内容能够看作是一个promise对象中的resolve,因此是当即执行的,属于主线程,await之后的会被丢进微任务中。

那么咱们能够肯定首先执行主线程中的任务即1,4,6,12;这时候settimeout中的内容被丢进宏任务,5,7,8,13被丢进微任务,又由于8是在await的promise对象的then以后,await的做用是其后的代码执行完毕之后才执行下一行代码,因此8被丢进了更后一层的微任务,因此如今会先按顺序执行微任务5,7,13,而后执行完之后再执行8;当这次循环汇中的微任务执行完之后开始执行宏任务,将宏任务中的内容归入主线程,按照以前的方式先执行主线程,遇到异步将异步丢进event table,因此按顺序执行时9,10,2,3,最后剩下promise的then在微任务中,最后执行11。

Async函数使用注意

  • 若是await后面的异步操做出错,那么等同于async函数返回的 Promise 对象被reject。因此最好把await命令放在try...catch代码块中。

  • 多个await命令后面的异步操做,若是不存在继发关系,最好让它们同时触发。

  • await命令只能用在async函数之中,若是用在普通函数,就会报错。

  • async 函数能够保留运行堆栈。

  • 还有一点须要注意的是咱们经过上边那段demo代码能够发现若是你await后边跟的是一个没有.then()的promise对象其实仍是至关于同步,因此要想要获得await后的代码全都执行完毕的结果必定要将异步代码放到.then()中并暴露出来,这样await后的代码才会在其异步全都执行完之后再执行。

Async函数的实现原理

async function fn(args) {
  // ...
}

// 等同于

function fn(args) {
  return spawn(function* () {
    // ...
  });
}
function spawn(genF) {
  return new Promise(function(resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}

与其余异步写法对比

// Promise
function chainAnimationsPromise(elem, animations) {
  // 变量ret用来保存上一个动画的返回值
  let ret = null;

  // 新建一个空的Promise
  let p = Promise.resolve();

  // 使用then方法,添加全部动画
  for(let anim of animations) {
    p = p.then(function(val) {
      ret = val;
      return anim(elem);
    });
  }

  // 返回一个部署了错误捕捉机制的Promise
  return p.catch(function(e) {
    /* 忽略错误,继续执行 */
  }).then(function() {
    return ret;
  });
}
// Generator 函数
function chainAnimationsGenerator(elem, animations) {
  return spawn(function*() {
    let ret = null;
    try {
      for(let anim of animations) {
        ret = yield anim(elem);
      }
    } catch(e) {
      /* 忽略错误,继续执行 */
    }
    return ret;
  });
}
// async 函数
async function chainAnimationsAsync(elem, animations) {
  let ret = null;
  try {
    for(let anim of animations) {
      ret = await anim(elem);
    }
  } catch(e) {
    /* 忽略错误,继续执行 */
  }
  return ret;
}

经过对比咱们能够发现async函数写法简洁语义明确,能够极大地减小代码量,而且让代码的可读性提升。在阮大神的《ECMAScript 6 入门》中还介绍了异步遍历器,for await...of,异步generator等等,感兴趣能够再详细研究一下。

相关文章
相关标签/搜索