async与await详解

mac2025-08-13  2

async:真正意义上去解决异步回调的问题,同步流程表达异步操作

语法:

async function foo(){ await 异步操作; await 异步操作; }

特点:     1、不需要像Generator去调用next方法,遇到await等待,当前的异步操作完成就往下执行     2、返回的总是Promise对象,可以用then方法进行下一步操作     3、async取代Generator函数的星号*,await取代Generator的yield     4、语意上更为明确,使用简单,经临床验证,暂时没有任何副作用

使用promise解决回调地狱

doSomething() .then(result => doSomethingElse(result)) .then(newResult => doThirdThing(newResult)) .then(finalResult => { console.log(`Got the final result: ${finalResult}`) }) .catch(failureCallback)

使用async与await解决 

async function request () { try { const result = await doSomething() const newResult = await doSomethingElse(result) const finalResult = await doThirdThing(newResult) console.log('Got the final result: ' + finalResult) } catch (error) { failureCallback(error) }

async

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。 async function timeout() {   return 'hello world'; } console.log(timeout())

它的调用会返回一个promise 对象

async函数内部 return 语句返回的值,会成为then方法回调函数的参数。 async function f() { return 'hello world'; } f().then(v => console.log(v)) // "hello world" async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。 async function f() { throw new Error('出错了'); } f().then( v => console.log(v), e => console.log(e) ) // Error: 出错了 async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。 async function getTitle(url) { let response = await fetch(url); let html = await response.text(); return html.match(/<title>([\s\S]+)<\/title>/i)[1]; } getTitle('https://tc39.github.io/ecma262/').then(console.log) // "ECMAScript 2017 Language Specification"

上面代码中,函数getTitle内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行then方法里面的console.log。

await

正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。 async function f() { // 等同于 // return 123; return await 123; } f().then(v => console.log(v)) // 123 async function f2() { var y = await 20; console.log(y); // 20 } f2(); await 操作符用于等待一个Promise 对象。它只能在异步函数 async function 中使用。await 关键字只在异步函数内有效。如果你在异步函数外使用它,会抛出语法错误await 表达式会暂停当前async function的执行,等待 Promise 处理完成。若 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值,继续执行async function。若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。 async function f() { await Promise.reject('出错了'); } f() .then(v => console.log(v)) .catch(e => console.log(e)) // 出错了

上面代码中,await语句前面没有return,但是reject方法的参数依然传入了catch方法的回调函数。这里如果在await前面加上return,效果是一样的。

任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。 async function f() { await Promise.reject('出错了'); await Promise.resolve('hello world'); // 不会执行 } 希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。 async function f() { try { await Promise.reject('出错了'); } catch(e) { } return await Promise.resolve('hello world'); } f() .then(v => console.log(v)) // hello world

          另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。

async function f() { await Promise.reject('出错了') .catch(e => console.log(e)); return await Promise.resolve('hello world'); } f() .then(v => console.log(v)) // 出错了 // hello world

需要注意的点

第一点,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。

async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } } // 另一种写法 async function myFunction() { await somethingThatReturnsAPromise() .catch(function (err) { console.log(err); }); }

第二点,多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

let foo = await getFoo(); let bar = await getBar();

上面代码中,getFoo和getBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。

// 写法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 写法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise;

上面两种写法,getFoo和getBar都是同时触发,这样就会缩短程序的执行时间。

第三点,await命令只能用在async函数之中,如果用在普通函数,就会报错。

async function dbFuc(db) { let docs = [{}, {}, {}]; // 报错 docs.forEach(function (doc) { await db.post(doc); }); }

上面代码会报错,因为await用在普通函数之中了。但是,如果将forEach方法的参数改成async函数,也有问题。

function dbFuc(db) { //这里不需要 async let docs = [{}, {}, {}]; // 可能得到错误结果 docs.forEach(async function (doc) { await db.post(doc); }); }

上面代码可能不会正常工作,原因是这时三个db.post操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用for循环。

async function dbFuc(db) { let docs = [{}, {}, {}]; for (let doc of docs) { await db.post(doc); } }

如果确实希望多个请求并发执行,可以使用Promise.all方法。当三个请求都会resolved时,下面两种写法效果相同。

async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = await Promise.all(promises); console.log(results); } // 或者使用下面的写法 async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = []; for (let promise of promises) { results.push(await promise); } console.log(results); }

第四点,async 函数可以保留运行堆栈。

const a = () => { b().then(() => c()); };

上面代码中,函数a内部运行了一个异步任务b()。当b()运行的时候,函数a()不会中断,而是继续执行。等到b()运行结束,可能a()早就运行结束了,b()所在的上下文环境已经消失了。如果b()或c()报错,错误堆栈将不包括a()。

现在将这个例子改成async函数。

const a = async () => { await b(); c(); };

上面代码中,b()运行的时候,a()是暂停执行,上下文环境都保存着。一旦b()或c()报错,错误堆栈将包括a()。

 

实现休眠效果:借助await命令就可以让程序停顿指定的时间。

function sleep(interval) { return new Promise(resolve => { setTimeout(resolve, interval); }) } // 用法 async function one2FiveInAsync() { for(let i = 1; i <= 5; i++) { console.log(i); await sleep(1000); } } one2FiveInAsync();
最新回复(0)