Async/await

async/await 是一种特殊的语法,能够更好的处理promise,可以让你编写基于Promise的代码像同步一样。

async

async 关键字放在函数之前,使得该函数总是返回一个promise对象。如果代码中显式 return 了一个值,那么函数的返回值将会被自动包装成resolved状态下的promise对象。

例如:

async function fn() {
  return 1;
}

fn().then(res => {
    console.log(res) // 1
})
async函数的使用方式
// 函数声明式
async function fn() {  }

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

// ArrowFunc
const fn = async () => {}

// 对象中定义
let obj = {
    async fn() {}
}

当async函数被调用执行的时候,会返回一个Promise的实例。当函数返回值时,promise会执行。如果函数在执行中抛出错误,那么会进入promise的rejected流程。

比如:

const foo = async () => { 
    throw new Error('err');
}

foo()
    .then(res => {
        console.log(res);
    })
    .catch(err=> {
        console.log(err) // Error: err
    })

await

await关键字只能在async函数中使用。可以用来等待Promise状态变成resolved并有返回值。await后面通常跟的是一个promise对象,如果不是,会立即被包装成resoled状态的promise。

async function foo() {
    let result = await 'ok'
    console.log(result); // ok
}
foo();
  1. 当用变量接收await的返回值时,值为promise为resolved状态下的值。
const foo = async () => {
    let result = await Promise.resolve('ok');
    console.log(result); // ok
}
foo();
  1. 函数执行中,遇到await,会首先返回promise,状态为pending,并且下面的代码会停止执行,待awiat后面的Promise执行完毕后才继续执行下面的代码,直到函数体中的代码全部执行完毕后,函数返回的promise状态才变成resolved。
function bar() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('ok');
        }, 1000);
    })
}

async function foo() {
    let result = await bar();
    console.log(result); // ok
    console.log('last'); 
}

console.log(foo());
 

输出顺序:

Markdown

首先打印出pending状态的promise对象。

等到await后面的异步函数执行完后,才依次打印出ok、last,同时Promise的状态成为resolved。

Markdown

由此可以看出,当在async函数中运行,当遇到await关键字时,会等待关键字后面的函数执行完毕才运行后面的代码。当所有的执行完后函数返回的Promise对象的状态才变成resolved。

异常处理

我们知道在promise中是通过catch来捕获异常的。但是在async中则使用try/catch来捕获异常。

  1. 如果await后面的 promise 正常resolve,await promise便会返回结果。但是在reject的情况下,便会抛出异常,并且这种异常需要用try/catch来捕获,否则会导致进程崩溃。
const baz = () => {
    return Promise.reject('Oops');
}
const foo = async () => { 
    try {
        await baz();
    } catch(e) {
        // e 为 baz()返回的promise对象中 reject出来的值
        console.log(e); // Oops
    }
}
foo();
  1. 如果try中有多个await,其中一个await后面的promise为reject,那么在catch中会抛出第一个异常。
    如下:
// 异步操作,返回promise对象
const bar = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('Oops bar error');
        }, 1000)
    })
}

const baz = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('Oops baz error');
        }, 1000)
    })
}
    
const foo = async () => { 
    try {
        await bar();
        await baz();
    } catch(e) {
        // e 为 bar()返回的promise对象中 reject出来的值
        console.log(e); // Oops bar error
    }
}

foo();

由此可以如果有多个 await 后面的promise都为reject状态时,只能捕获第一个异常。

  1. 以下这种方式都会捕获到。由于是同步的效果,所以第二次捕获的异常是第一次捕获1s后。
const fn = async () => {
    try {
        await bar();
    } catch (e) {
        console.log(e) // Oops bar error
    }

    try {
        await baz();
    } catch (e) {
        console.log(e) // Oops baz error
    }
}

fn();

// 第二次和第一次的顺序相隔1s
 
  1. 如果await后面的promise中抛出异常,那么等同于async函数返回的 Promise 对象被reject。如下:
async function foo() {
  await new Promise((resolve, reject) => {
    throw new Error('Oops');
  });
}

foo()
    .then(res => console.log('ok', res))
    .catch(err => console.log('faile', err)) // faile Error: Oops

  1. try里边的promise中的异常不会在catch中捕获,因为异常发生在promise中,只能通过promise的catch()捕获。
const foo = () => {
    return new Promise((resolve, reject) => {
        resolve('ok');
    })
}

const bar = () => {
    try {
        foo()
            .then(res => {
                let data = JSON.parse(res);
            }).catch(err => {
                console.log(err); //  SyntaxError: Unexpected token o in JSON at position 0
            })
    } catch(e) {
        // 这里不会捕获到promise中的异常
        console.log(e);
    }
}

bar();

并行和串行

  1. 串行
    如果有多个await,那么会按照顺序一个个的执行。
const p1 = async () => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve({ name: 'Rose' })
        }, 1000);
    })
}

const p2 = async () => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve({ name: 'Rose' })
        }, 1000);
    })
}

const getInfo = async () => {
    console.time('total time');
    let p1Info = await p1();
    let p2Info = await p2();
    console.timeEnd('total time'); // total time: 2006.705810546875ms
} 

getInfo()

因为每个await后面的异步函数都会延时1s,所以总耗时大于2s。

  1. 并行

如果有多个await,那么先将await后面的异步函数的返回值保存变量中。

const getInfo = async() => {
    console.time('total time');
    const [p1Info, p2Info] = await Promise.all([
        p1(),
        p2()
    ])
    console.timeEnd('total time'); // total time: 1003.5810546875ms
}
getInfo()

Promise.all()返回值是一个新的promise对象,值为数组。

  1. 循环中的串行
const p = async (name) => {
    return new Promise(resolve => {
        let user = {name};
        setTimeout(() => {
            resolve(user)
        }, 1000)
    })
}
let arr = ['bob', 'mike'];
const getInfo = async() => {
    console.time('total time');

    for (let name of arr) {
        const p3Info = await p(name);
        // 每隔1s输出一次
        console.log(p3Info.name);
    }
    console.timeEnd('total time'); // total time: 2007.77783203125ms
}

输出顺序:
bob
mike
total time: 2007.77783203125ms

  1. 循环中串行改并行
const getInfo = async() => {
    console.time('total time');

    // 将所有的promise对象保存在数组中
    let promises = arr.map(name => {
        return p3(name);
    })
    
    for(let promise of promises) {
        const Info = await promise;
        console.log(Info.name);
    }
    console.timeEnd('total time'); // total time: 1006.291015625ms
}

总结:

async函数之前的关键字有两个作用:

  1. 使它总是返回一个promise。
  2. 允许在其中使用await。

await promise之前的关键字使得JavaScript等待,直到这个promise的状态为resolved

  1. 如果是reject,则产生异常,需通过try/catch捕获。
  2. 否则,它返回结果,所以我们可以将它的值赋值给一个变量。

async/await使我们少写promise.then/catch,但是不要忘记它们是基于promise的。

此外,网上很多关于promise的文章都会提到ajax的回调地狱,以此来说明promise的诞生只是用来解决异步的,其实不然。promise 只是解决异步的一种方式。

如果使用async/await,不仅代码简介,甚至有同步的feel。

promise是一种语法、一种形式,目前很多东西都是基于promise实现的,比如:jquery中的ajax,fetch,以及vue react中的很多功能也都使用了promise。所以promise是最最基本的。

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

推荐阅读更多精彩内容