ES6 async 函数

async函数


标题内容
async的引入AsyncFunction构造函数, Generator
async的含义async函数是什么?
async的特点内置执行器, 更好的语义, 更广的适用性, 返回值是Promise
async的实现原理Generator函数, 自动执行器

async的引入

  • ES2017标准引入了async函数,使得异步操作变得更加方便。也可以说是Generator的语法糖。

(了解)AsyncFunction构造函数

  • AsyncFunction构造函数用来创建新的异步函数对象,JavaScript中每个异步函数都是AsyncFunction的对象。
async function asyncFunction(){}
asyncFunction.constructor
// AsyncFunction() { [native code] }
  • 注意: AsyncFunction并不是全局对象,需要通过下面的方法来获取:
Object.getPrototypeOf(async function(){}).constructor
// AsyncFunction() { [native code] }
AsyncFunction语法
new AsyncFunction([arg1[, arg2[, ...argN]],] functionBody)
AsyncFunction参数
  • arg1, arg2, ... argN: 函数的参数名,它们是符合JavaScript标示符规范的一个或多个用逗号隔开的字符串。例如x, theValuea, b

  • functionBody: 一段字符串形式的JavaScript语句,这些语句组成了新函数的定义。

function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}

var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
var a = new AsyncFunction('a',
                          'b',
                          'return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b);');
a(10, 20).then(v => {
  console.log(v); // 4 秒后打印 30
});
  • 执行AsyncFunction构造函数的时候,会创建一个异步函数对象。但是这种方式不如先用异步函数表达式定义一个异步函数,然后再调用其来创建异步函数对象来的高效,因为第二种方式中异步函数是与其他代码一起被解释器解析的,而第一种方式的函数体是单独解析的。
  • 传递给AsyncFunction构造函数的所有参数,都会成为新函数中的变量变量的名称和定义顺序与各参数相同
  • 注意: 使用AsyncFunction构造函数创建的异步函数并不会在当前上下文中创建闭包,其作用域始终是全局的。因此运行的时候只能访问它们自己的本地变量全局变量,但不能访问构造函数被调用的那个作用域中的变量。这是它与eval不同的地方(eval函数最好永远也不要使用)。
/**
 * @description 自己的本地变量和全局变量
*/
function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  })
}

var c = 1;
var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;

var a = new AsyncFunction('a', 'b', 'c', 'return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b) + await resolveAfter2Seconds(c);');
a(10, 20, c).then(v => {
  console.log(v);
})
/**
 * @description 建议: 永远不要使用eval
*/
function test() {
  var x = 2, y = 4;
  console.log(eval('x + y'));  // 直接调用,使用本地作用域,结果是 6
  var geval = eval; // 等价于在全局作用域调用
  console.log(geval('x + y')); // 间接调用,使用全局作用域,throws ReferenceError 因为`x`未定义
  (0, eval)('x + y'); // 另一个间接调用的例子
}
  • 调用AsyncFunction构造函数时可以省略new,其效果是一样的。

async函数

  • async函数是使用async关键字声明的函数。async函数是AsyncFunction构造函数的实例,并且其中允许使用await关键字。
  • async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise
function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}

async function asyncCall() {
  console.log('calling');
  const result = await resolveAfter2Seconds();
  console.log(result);
}

aysncCall();// 先输出calling 2秒之后输出resolved
async函数的语法
async function name([param[, param[, ... param]]]) {
  statements
}
async函数的参数
  • name: 函数名称。
  • param: 要传递给函数的参数的名称。
  • statements: 包含函数主体的表达式。可以使用await机制。
返回值
  • 一个Promise, 这个promise要么会通过一个由async函数返回的值被解决, 要么会通过一个从async函数中抛出的(或其中没有被捕获到的)异常被拒绝。
async function foo() {
  return 1
}
// 等价于
function foo() {
  return Promise.resolve(1)
}
async function foo() {
  await 1
}
// 等价于
function foo() {
  return Promise.resolve(1).then(() => undefined)
}

async的含义

  • async函数的函数体可以被看作是由0个或者多个await表达式分割开来的。从第一行代码直到(并包括)第一个await表达式(如果有的话)都是同步运行的。这样的话,一个不含await表达式的async函数是会同步运行的。然而, 如果函数体内有一个await表达式, async函数就一定会异步执行
// promise链不是一次就构建好的,相反,promise链是分阶段构造的,因此在处理异步函数时必须注意对错误函数的处理
async function foo() {
  const result1 = await new Promise((resolve) => setTimeout(() => resolve('1')))
  const result2 = await new Promise((resolve) => setTimeout(() => resolve('2')))
}
foo()
  • 上面的执行顺序:

    1. foo函数的第一行将会同步执行, await将会等待promise的结束。然后暂停通过foo的进程, 并将控制权交还给调用foo的函数。
    1. 一段时间后, 当第一个promise完结的时候, 控制权将重新回到foo函数内。示例中将会1(promise状态为fulfilled)作为结果返回给await表达式的左边即result1。接下来函数会继续进行, 到达第二个await区域, 此时foo函数的进程将再次被暂停。
    1. 一段时间后, 同样当第二个promise完结的时候, result2将被赋值为2, 之后函数将会正常同步执行, 将默认返回undefined
// 在promise链上配置了.catch处理程序,将抛出未处理的promise错误。这是因为p2返回的结果不会被await处理
async function foo() {
  const p1 = new Promise((resolve) => setTimeout(() => resolve('1'), 1000))
  const p2 = new Promise((_,reject) => setTimeout(() => reject('2'), 500))
  const results = [await p1, await p2] // 不推荐使用这种方式,请使用 Promise.all或者Promise.allSettled 
}
foo().catch(() => {}) // 捕捉所有的错误...
// 函数声明
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('jack').then(...);

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

async的特点

内置执行器

  • Generator函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
const asyncFunction = async function () {
  const f1 = await 1
  const f2 = await 2
  console.log(f1.toString());
  console.log(f2.toString());
};

asyncFunction();
// 1
// 2
  • 上面的代码调用了asyncFunction函数, 然后它就会自动执行, 输出最后结果。这完全不像Generator函数, 需要调用next方法,或者使用co模块, 才能真正执行, 得到最后结果。
var gen = function* () {
  var f1 = yield 1
  var f2 = yield 2
  console.log(f1.toString())
  console.log(f2.toString())
}

var co = require('co'); // Generatir直接执行需要使用co模块
co(gen).then(function() {
  console.log('Generator 函数执行完成');
});
// 1
// 2
// Generator 函数执行完成

更好的语义

  • asyncawait, 比起星号yield, 语义更清楚了。async表示函数里有异步操作, await表示紧跟在后面的表达式需要等待结果。
const asyncFunction = async function() {
  const f1 = await 1 // 紧跟在后面的表达式需要等待结果
  const f2 = await 2
  console.log(f1.toString())
  console.log(f2.toString())
}
const genFunction = function* () {
  const f1 = yield 1
  const f2 = yield 2
  console.log(f1.toString())
  console.log(f2.toString())
}

更广的实用型

  • co模块约定, yield命令后面只能是Thunk函数或者Promise对象, 而async函数的await命令后面, 可以是Promise对象原始类型的值(数值字符串布尔值, 但这时会自动转成立即resolved的Promise对象)。

返回值是Promise

  • async函数的返回值是Promise对象, 这比Generator函数的返回值是Iterator对象方便多了。你可以用then方法指定下一步的操作。
function getStockSymbol(name) {
  const symbolName = Symbol(name)
  return symbolName
}

function getStockUser(symbol) {
  return symbol
}

async function getUserByName(name) {
  const symbol = await getStockSymbol(name);
  const stockUser = await getStockUser(symbol);
  console.log('stockUser', stockUser)
  return stockUser;
}

getUserByName('JackDan').then(function (result) {
  console.log(result)
})
  • 进一步说, async函数完全可以看作多个异步操作, 包装成的一个Promise对象, 而await命令就是内部then命令的语法糖。
function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms)
  console.log(value)
}

asyncPrint('hello world', 5000);
// 间隔5秒 输出hello world
  • 由于async函数返回的是Promise对象,可以作为await命令的参数。
async function timeout(ms) {
  await new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 3000);
// 间隔3秒 输出hello world

async的实现原理

  • async函数的实现原理, 就是将Generator函数和自动执行器, 包装在一个函数里。
async function asyncFunction() {
  // ...
}

// 等同于

function asyncFunction(args) {
  return spawn(function* () {
    // ...
  })
}


function spawn(genFunction) {
  // 返回一个Promise对象, async返回的也是Promise对象
  return new Promise(function(resolve, reject) {
    const _gen = genFunction();
    function step(nextFunction) {
      let _next;
      try {
        _next = nextFunction();
      } 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); });
  })
}

来个示例

var resolveAfter2Seconds = function () {
  console.log("starting slow promise");
  return new Promise((resolve) => {
    setTimeout(function() { 
      resolve('slow');
      console.log("slow promise is done");
    }, 20000);
  });
};

var resolveAfter1Seconds = function () {
  console.log("starting fast promise");
  return new Promise((resolve) => {
    setTimeout(function() {
      resolve('fast');
      console.log("fast promise is done");
    }, 1000);
  });
};

var parallel = async function() {
  console.log('==PARALLEL with await Promise.all==');
  
  await Promise.all([
    (async() => console.log(await resolveAfter2Seconds()))(),
    (async() => console.log(await resolveAfter1Seconds()))()
  ]);
}

setTimeout(parallel, 10000);
var resolveAfter2Seconds = function () {
  console.log("starting slow promise");
  return new Promise((resolve) => {
    resolve('slow');
    console.log("slow promise is done")
  }, 2000);
}

var resolveAfter1Seconds = function () {
  console.log("starting fast promise");
  return new Promise((resolve) => {
    resolve('fast');
    console.log("fast promise is done");
  }, 1000);
}

var sequentialStart = async function() {
  console.log('==SEQUENTIAL START==');

  // 1. Execution gets here almost instantly
  const slow = await resolveAfter2Seconds();
  console.log(slow); // 2. this runs 2 seconds after 1.

  const fast = await resolveAfter1Second();
  console.log(fast); // 3. this runs 3 seconds after 1.
}
var resolveAfter2Seconds = function () {
  console.log("starting slow promise");
  return new Promise((resolve) => {
    resolve('slow');
    console.log("slow promise is done")
  }, 2000);
}

var resolveAfter1Seconds = function () {
  console.log("starting fast promise");
  return new Promise((resolve) => {
    resolve('fast');
    console.log("fast promise is done");
  }, 1000);
}

var concurrentStart = async function() {
  console.log('==CONCURRENT START with await==');
  const slow = resolveAfter2Seconds(); // starts timer immediately
  const fast = resolveAfter1Second(); // starts timer immediately

  // 1. Execution gets here almost instantly
  console.log(await slow); // 2. this runs 2 seconds after 1.
  console.log(await fast); // 3. this runs 2 seconds after 1., immediately after 2., since fast is already resolved
}

JackDan Thinking
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction