async/await

有一种特殊的语法可以以更舒适的方式处理promises,称为“async / await”。理解和使用起来非常简单。

async-await是promise和generator的语法糖。只是为了让我们书写代码时更加流畅,当然也增强了代码的可读性。简单来说:async-await 是建立在 promise机制之上的,并不能取代其地位。

Async functions

先从关键字async说起,它被放在一个函数前面,像下面这样:

async function f(){    
    return ‘Hello world’;
}

函数前面的“async”一词意味着一件简单的事:函数总是返回一个promise。即使函数实际返回非promise值,在函数定义前加上“async”关键字也会指示JavaScript自动将该值包装在已解析的promise中。

例如,下面的代码返回一个已解析的promise,其结果是1:

async function f() { 
     return 1; 
}
f().then(alert);// 1

也可以直接返回一个promise,例如:

async function f () {
    return Promise.resolve(1);
}

f().then(alert);// 1

因此,async确保函数返回一个promise,并在其中包含非promise。不仅如此。还有另一个关键字,await,它只能在async函数内部运行。

Await

语法:

// await只能放在async函数内部运行
let value = await promise;

await 后面可以跟任何的JS 表达式。虽然说 await 可以等很多类型的东西,但是它最主要的意图是用来等待 Promise 对象的状态被 resolved。如果await的是 promise对象会造成异步函数停止执行并且等待 promise 的解决,如果等的是正常的表达式则立即执行。

例如:

function sleep(second) {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve(' enough sleep~'); }, second); }
    )
}

function normalFunc() {
    console.log('normalFunc');
}

async function awaitDemo() {
    await normalFunc();
    console.log('something, ~~');
    let result = await sleep(2000);
    console.log(result);// 两秒之后会被打印出来
}

awaitDemo();

注意:await字面意思是让JavaScript等到承诺结束,然后继续结果。这不会占用任何CPU资源,因为引擎可以同时执行其他任务:执行其他脚本,处理事件等。

这只是得到promise.then结果的更优雅的语法,易于读写。

不能await在常规功能中使用

如果我们尝试await在非异步函数中使用,则会出现语法错误:

function f () {
    let promise = Promise.resolve(1);
    let result = await promise; // Uncaught SyntaxError: await is only valid in async function
}

如果我们没有在函数之前写async,我们将得到此错误。如上所述,await只能在一个async function内部工作。

一个promise链式操作的例子,若用 ES5实现会有多层的回调,若用Promise 实现也需要多个then。一个是代码横向发展,另一个是纵向发展。

fetch('/article/promise-chaining/user.json')

  .then(response => response.json())

  .then(user => fetch(`https://api.github.com/users/${user.name}`))

  .then(response => response.json())

  .then(githubUser => new Promise(function (resolve, reject) {

    let img = document.createElement('img');

    img.src = githubUser.avatar_url;

    img.className = "promise-avatar-example";

    document.body.append(img);

    setTimeout(() => {

      img.remove();

      resolve(githubUser);

    }, 3000);

  }))

  // triggers after 3 seconds

  .then(githubUser => alert(`Finished showing ${githubUser.name}`));

用async/await重写它:

1.将.then()替换为await

2.让函数变成async

async function showAvatar() {
  // read our JSON
  let response = await fetch('/article/promise-chaining/user.json');
  let user = await response.json();

  // read github user
  let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
  let githubUser = await githubResponse.json();

  // show the avatar
  let img = document.createElement('img');
  img.src = githubUser.avatar_url;
  img.className = "promise-avatar-example";
  document.body.append(img);

  // wait 3 seconds
  await new Promise((resolve, reject) => setTimeout(resolve, 3000));
  img.remove();
  return githubUser;
}
showAvatar();

用了await之后代码相对来说更简洁和易读。

await 不能放在顶级作用域

let response = await fetch('/article/promise-chaining/user.json');

let user = await response.json();

可以将它包装成一个匿名的异步函数,如下所示:

(async () => {
  let response = await fetch('/article/promise-chaining/user.json');
  let user = await response.json();

  ...

})();

await接受thenables

就像promise.then,await也允许使用thenable对象(那些具有可调用的then方法的对象)。同样,第三方对象可能不是一个promise,但是promise的兼容性表示,如果它支持.then方法,那么它就能用于await。

例如,这里await接受了new Thenable(1)

class Thenable {

  constructor(num) {
    this.num = num
  }

  then(resolve, reject) {
    alert(resolve) // function() {native code}

    // 1000ms后将this.num*2作为resolve值
    setTimeout(() => { resolve(this.num * 2), 1000 }) // (*)
  }
}

async function f() {
  // 等待1s,result变为2
  let result = await new Thenable(1)

  alert(result)
}

f()

如果await得到了一个带有then方法的非promise对象,它将会调用提供原生函数resolve、reject作为参数的方法,然后await一直等待,直到他们其中的一个被调用(在上面的例子它发生在(*)行)。

async方法

一个class方法同样能够使用async,只需要将async放在它之前就可以

就像这样:

class Waiter {
  async wait() {
    return await Promise.resolve(1)
  }
}

new Waiter().wait().then(alert) // 1

含义相同:它确保返回的值是promise并启用await。

错误处理

如果一个promise正常resolve,那么await返回这个结果,但是在reject的情况下会抛出一个错误,就好像在那一行有一个throw语句一样。

async function f() {
  await Promise.reject(new Error('whoops!'))
}

和这个一样

async function f() {
  throw new Error("Whoops!");
}

在实际情况中,promise在reject抛出错误之前可能需要一段时间,所以await将会等待,然后才抛出一个错误。

我们可以使用try - catch语句捕获错误,就像在正常抛出中处理异常一样:

async function f() {
  try {
    let response = await fetch('http://no-such-url');
  } catch (err) {
    alert(err); // TypeError: failed to fetch
  }
}

f();

如果出现错误,控件将跳转到该catch块。我们还可以包装多行:

async function f() {
  try {
    let response = await fetch('/no-user-here');
    let user = await response.json();
  } catch (err) {
    // catches errors both in fetch and response.json
    alert(err);
  }
}

f();

如果我们没有try..catch,则异步函数调用生成的promise f()将被拒绝。我们可以附加.catch处理它:

async function f() {
  let response = await fetch('http://no-such-url');
}

// f() becomes a rejected promise

f().catch(alert); // TypeError: failed to fetch // (*)

如果我们忘记添加.catch,我们就会得到一个未被处理的promise错误(能够在控制台里看到它),这时我们可以通过使用一个全局的事件处理器去捕获错误。

async/await 和 promise.then/catch 对比

当我们使用时async/await,我们很少需要.then,因为await为我们处理了等待。我们可以使用常规 try..catch 代替 .catch 。这通常(并非总是)更方便。

但是在代码的顶层,当我们在任何async函数之外时,我们在语法上无法使用await,这时候使用.then/catch来处理最终结果或捕获错误是正常的做法。

async / await 适用于 Promise.all

当我们需要等待多个 promise 时,我们可以将它们放进Promise.all然后await:

function sleep(second) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('request done! ' + new Date().getTime())
            resolve();
        }, second);
    })
}

async function bugDemo() {
    let p1 = sleep(1000);
    let p2 = sleep(1000);
    let p3 = sleep(1000);
    await Promise.all([p1, p2, p3]);
    console.log('over~');
}

bugDemo();

await in for循环(await必须在async函数的上下文中)

// 正常 for 循环

async function forDemo() {
    let arr = [1,2,3,4,5];
    for(leti = 0; i < arr.length; i ++) {
        await arr[i]; 
     }
}

forDemo();//正常输出

// 因为想要炫技把 for循环写成下面这样
async function forBugDemo() {
    let arr = [1,2,3,4,5]; 
    arr.forEach(item=>{
        await item;
    });
}

forBugDemo(); // Uncaught SyntaxError: await is only valid in async function

总结

放在一个函数前的async有两个作用:

1.使函数总是返回一个promise

2.允许在这其中使用await

promise前面的await关键字能够使JavaScript等待,直到promise处理结束。然后:

1.如果产生一个错误或者reject,异常就产生了,就像在那个地方调用了throw error一样。

2.否则,它会返回一个结果,我们可以将它分配给一个值

参考文章:https://javascript.info/async-await#async-functions

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容