Async 函数的运用及简朴完成

处置惩罚回调地狱的异步操纵,Async 函数是终究要领,但相识天生器和 Promise 有助于邃晓 Async 函数道理。由于内容较多,分三部份举行,这是第三部份,引见 Async 函数相干。第一部份引见 Generator,第二部份引见 Promise。

在这部份中,我们会先引见 Async 函数的基础运用,然后会连系前两部份引见的天生器和 Promise 完成一个 async 函数。

1)Async 函数概览

1.1 观点

经由历程在平常函数前加async操纵符能够定义 Async 函数:

// 这是一个 async 函数
async function() {}

Async 函数体中的代码是异步实行的,不会壅塞背面代码实行,但它们的写法和同步代码相似。

Async 函数会 返回一个已完成的 promise 对象,现实在运用的时刻会和await操纵符合营运用,在引见await之前,我们先看看 async 函数自身有哪些特性。

1.2 Async 函数基础用法

1.2.1 函数体内没有 await

假如 async 函数体内假如没有await操纵符,那末它返回的 promise 对象状况和他的函数体内代码怎样写有关联,详细和 promise 的then()要领的处置惩罚体式格局雷同:

1)没有显式 return 任何数据

此时默许返回Promise.resolve():

var a = (async () => {})();

相当于

var a = (async () => {
  return Promise.resolve();
})();

此时 a 的值:

a {
  [[PromiseStatus]]: 'resolved',
  [[PromiseValue]]: undefined
}

2)显式 return 非 promise

相当于返回Promise.resolve(data)

var a = (async () => {
  return 111;
})();

相当于

var a = (async () => {
  return Promise.resolve(111);
})();

此时 a 的值:

a {
  [[PromiseStatus]]: 'resolved',
  [[PromiseValue]]: 111
}

3)显式 return promise 对象

此时 async 函数返回的 promise 对象状况由显现返回的 promise 对象状况决议,这里以被谢绝的 promise 为例:

var a = (async () => Promise.reject(111))();

此时 a 的值:

a {
  [[PromiseStatus]]: 'rejected',
  [[PromiseValue]]: 111
}

但现实运用中,我们不会向上面那样运用,而是合营await操纵符一同运用,不然像上面那样,和 promise 比拟,并没有上风可言。迥殊的,没有await操纵符,我们并不能用 async 函数处置惩罚相互依靠的异步数据的要求题目。

换句话说:我们不体贴 async 返回的 promise 状况(平常状况,async 函数不会返回任何内容,即默许返回Promise.resolve()),我们体贴的是 async 函数体内的代码怎样写,由于内里的代码能够异步实行且不壅塞 async 函数背面代码的实行,这就为写异步代码制造了前提,而且誊写形式上和同步代码一样。

1.2.2 await 引见

await操纵符运用体式格局以下:

[rv] = await expression;

expression:能够是任何值,但平常是一个 promise;

rv: 可选。假如有且 expression 黑白 promise 的值,则 rv 即是 expression 自身;不然,rv 即是 兑现 的 promise 的值,假如该 promise 被谢绝,则抛个非常(所以await平常被 try-catch 包裹,非常能够被捕获到)。

但注重await必须在 async 函数中运用,不然会报语法毛病

1.2.3 await 运用

看下面代码例子:

1)expression 后为非 promise

(async () => {
  const b = await 111;
  console.log(b); // 111
})();

直接返回这个 expression 的值,即,打印 111

2)expression 为兑现的 promise

(async () => {
  const b = await Promise.resolve(111);
  console.log(b); // 111
})();

返回兑现的 promise 的值,所以打印111

3)expression 为谢绝的 promise

(async () => {
  try {
    const b = await Promise.reject(111);

    // 前面的 await 失足后,当前代码块背面的代码就不实行了
    console.log(b); // 不实行
  } catch (e) {
    console.log("失足了:", e); // 失足了:111
  }
})();

假如await背面的 promise 被谢绝或自身代码实行失足都邑抛出一个非常,然后被 catch 到,而且,和当前await同属一个代码块的背面的代码不再实行。

2)Async 函数处置惩罚异步要求

2.1 相互依靠的异步数据

在 promise 中我们处置惩罚相互依靠的异步数据运用链式挪用的体式格局,虽然比拟回调函数已优化许多,但誊写及邃晓上照样没有同步代码直观。我们看下 async 函数怎样处置惩罚这个题目。

先回忆下需求及 promise 的处置惩罚方案:

需求:_要求 URL1 获得 data1;要求 URL2 获得 data2,但 URL2 = data1[0].url2;要求 URL3 获得 data3,但 URL3 = data2[0].url3_。

运用 promise 链式挪用能够如许写代码:

promiseAjax 在
第二部份引见 promise 时在 3.1 中定义的,经由历程 promise 封装的 ajax GET 要求。

promiseAjax('URL1')
  .then(data1 => promiseAjax(data1[0].url2))
  .then(data2 => promiseAjax(data2[0].url3);)
  .then(console.log(data3))
  .catch(e => console.log(e));

假如运用 Async 函数则能够像同步代码的一样写:

async function() {
  try {
    const data1 = await promiseAjax('URL1');
    const data2 = await promiseAjax(data1[0].url);
    const data3 = await promiseAjax(data2[0].url);
  } catch (e) {
    console.log(e);
  }
}

之所以能够如许用,是由于只要当前await守候的 promise 兑现后,它背面的代码才会实行(或许抛失足误,背面代码都不实行,直接去到 catch 分支)。

这里有两点值得关注:

1)await帮我们处置惩罚了 promise,要么返回兑现的值,要么抛出非常;
2)await在守候 promise 兑现的同时,全部 async 函数会挂起,promise 兑现后再从新实行接下来的代码。

关于第 2 点,是否是想到了天生器?在 1.4 节中我们会经由历程天生器 + promise 本身写一个 async 函数。

2.2 无依靠关联的异步数据

Async 函数没有Promise.all()之类的要领,我们需要写多几个 async 函数。

能够借助Promise.all()在同一个 async 函数中并行处置惩罚多个无依靠关联的异步数据,以下:

async function fn1() {
  try {
    const arr = await Promise.all([
      promiseAjax("URL1"),
      promiseAjax("URL2"),
    ]);

    // ... do something
  } catch (e) {
    console.log(e);
  }
}

谢谢
@贾顺名
批评

但现实开辟中假如异步要求的数据是营业不相干的,不引荐如许写,缘由以下:

把一切的异步要求放在一个 async 函数中相当于手动加强了营业代码的耦合,会致使下面两个题目:

1)写代码及猎取数据都不直观,特别要求多起来的时刻;
2)Promise.all内里写多个无依靠的异步要求,假如 个中一个被谢绝或发作非常,一切要求的结果我们都猎取不到

假如营业场景是不体贴上面两点,能够斟酌运用上面的写法,不然,每一个异步要求都放在差别的 async 函数中发出。

下面是离开写的例子:

async function fn1() {
  try {
    const data1 = await promiseAjax("URL1");

    // ... do something
  } catch (e) {
    console.log(e);
  }
}

async function fn2() {
  try {
    const data2 = await promiseAjax("URL2");

    // ... do something
  } catch (e) {
    console.log(e);
  }
}

3)Async 模仿完成

3.1 async 函数处置惩罚异步数据的道理

我们先看下 async 处置惩罚异步的道理:

  • async 函数碰到await操纵符会挂起;
  • await背面的表达式求值(平常是个耗时的异步操纵)前 async 函数一向处于挂起状况,防止壅塞 async 函数背面的代码;
  • await背面的表达式求值求值后(异步操纵完成),await能够对该值做处置惩罚:假如黑白 promise,直接返回该值;假如是 promsie,则提取 promise 的值并返回。同时通知 async 函数接着实行下面的代码;
  • 那里出现非常,完毕 async 函数。

await背面的谁人异步操纵,往往是返回 promise 对象(比方 axios),然后交给 await 处置惩罚,毕竟,async-await 的设想初志就是为了处置惩罚异步要求数据时的回调地狱题目,而运用 promise 是症结一步。

async 函数自身的行动,和天生器相似;而await守候的平常是 promise 对象,也正因如此,常说 async 函数是 天生器 + promise 连系后的语法糖。

既然我们知道了 async 函数处置惩罚异步数据的道理,接下来我们就简朴模仿下 async 函数的完成历程。

3.2 async 函数简朴完成

这里只模仿 async 函数合营await处置惩罚收集要求的场景,而且要求终究返回 promise 对象,async 函数自身返回值(已完成的 promise 对象)及更多运用场景这里没做斟酌。

所以接下来的 myAsync 函数只是为了申明 async-await 道理,不要将其用在临盆环境中。

3.2.1 代码完成

/**
 * 模仿 async 函数的完成,该段代码取自 Secrets of the JavaScript Ninja (Second Edition),p159
 */
// 吸收天生器作为参数,发起先移到背面,看下天生器中的代码
var myAsync = generator => {
  // 注重 iterator.next() 返回对象的 value 是 promiseAjax(),一个 promise
  const iterator = generator();

  // handle 函数掌握 async 函数的 挂起-实行
  const handle = iteratorResult => {
    if (iteratorResult.done) return;

    const iteratorValue = iteratorResult.value;

    // 只斟酌异步要求返回值是 promise 的状况
    if (iteratorValue instanceof Promise) {
      // 递归挪用 handle,promise 兑现后再挪用 iterator.next() 使天生器继承实行
      // ps.原书then末了少了半个括号 ')'
      iteratorValue
        .then(result => handle(iterator.next(result)))
        .catch(e => iterator.throw(e));
    }
  };

  try {
    handle(iterator.next());
  } catch (e) {
    console.log(e);
  }
};

3.2.2 运用

myAsync吸收的一个天生器作为入参,天生器函数内部的代码,和写原生 async 函数相似,只是用yield替代了await

myAsync(function*() {
  try {
    const a = yield Promise.resolve(1);
    const b = yield Promise.resolve(a + 10);
    const c = yield Promise.resolve(b + 100);
    console.log(a, b, c); // 输出 1,11,111
  } catch (e) {
    console.log("失足了:", e);
  }
});

上面会打印1 11 111

假如第二个yield语句后的 promise 被谢绝Promise.reject(a + 10),则打印失足了:11

3.2.3 申明:

  • myAsync 函数接收一个天生器作为参数,掌握天生器的 挂起 可到达使全部 myAsync 函数在异步代码要求历程 挂起 的结果;
  • myAsync 函数内部经由历程定义handle函数,掌握天生器的 _挂起-实行_。

详细历程以下:

1)起首挪用generator()天生它的掌握器,即迭代器iterator,此时,天生器处于挂起状况;
2)第一次挪用handle函数,并传入iterator.next(),如许就完成天生器的第一次挪用的;
3)实行天生器,碰到yield天生器再次挂起,同时把yield后表达式的结果(未完成的 promise)传给 handle;
4)天生器挂起的同时,异步要求还在举行,异步要求完成(promise 兑现)后,会挪用handle函数中的iteratorValue.then()
5)iteratorValue.then()实行时内部递归挪用handle,同时把异步要求回的数据传给天生器(iterator.next(result)),天生器更新数据再次实行。假如失足直接完毕;
6)3、4、5 步反复实行,直到天生器完毕,即iteratorResult.done === true,myAsync 完毕挪用。

假如看不邃晓,可参考下
第一部份 天生器相干和
第二部份 Promise 相干。

参考

【1】[美]JOHN RESIG,BEAR BIBEAULT and JOSIP MARAS 著(2016),Secrets of the JavaScript Ninja (Second Edition),p159,Manning Publications Co.
【2】async function-MDN
【3】await-MDN
【4】邃晓 JavaScript 的 async/await

    原文作者:L小庸
    原文地址: https://segmentfault.com/a/1190000015735201
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞