async函数
async 函数
async() : Generator 函数的语法糖。(async
函数就是将 Generator 函数的星号(*
)替换成async
,将yield
替换成await
,仅此而已。)
async和await两种语法结合可以让异步代码像同步代码一样
// -返回值:promise对象 // -promise对象的结果,由async函数执行的返回值决定
async function fn() { // return 'hello world'; // return返回的结果如果不是一个promise类型的对象(例如:字符串),那么函数的返回结果就是一个成功的promise对象 // return返回的结果是一个promise对象,那么对象的结果由该对象决定 return new Promise((resolve, reject) => { resolve('成功的数据'); // reject('失败的数据'); }) } var result = fn(); console.log(result); // Promise {<pending>}
await
// -await必须写在async函数中 // -await右侧的表达式一般为promise对象 // -await返回的是promise成功的值 // -await的promise失败了,就会抛出异常,需要通过try...catch捕获处理
async
函数对 Generator 函数的改进,体现在以下四点。
(1)内置执行器。
Generator 函数的执行必须靠执行器,所以才有了co
模块,而async
函数自带执行器。也就是说,async
函数的执行,与普通函数一模一样,只要一行。
asyncReadFile();
(2)更好的语义。
async
和await
,比起*号和yield
,语义更清楚了。async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。
(3)更广的适用性。
co
模块约定,yield
命令后面只能是 Thunk 函数或 Promise 对象,而async
函数的await
命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
(4)返回值是 Promise。
async
函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then
方法指定下一步的操作。
进一步说,async
函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await
命令就是内部then
命令的语法糖。
基本用法
async
函数返回一个 Promise 对象,可以使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
下面是一个例子,指定多少毫秒后输出一个值。
function timeout(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function asyncPrint(value, ms) { await timeout(ms); console.log(value); } asyncPrint('hello world', 50);
上面代码指定 50 毫秒以后,输出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', 50);
async 函数有多种使用形式。
// 函数声明 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('jake').then(…); // 箭头函数 const foo = async () => {};
注意:
1、async函数在声明形式上和普通函数没有区别,函数声明式,函数表达式,对象方法,class方法和箭头函数等都可以声明async函数。
2.任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
3.async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
语法
async
函数的语法规则总体上比较简单,难点是错误处理机制。
async
函数返回一个 Promise 对象。
async
函数内部return
语句返回的值,会成为then
方法回调函数的参数。
async function foo() { return 'hello world'; } foo().then(v => console.log(v)); // hello world
async
函数内部抛出错误,会导致返回的 Promise 对象变为reject
状态。抛出的错误对象会被catch
方法回调函数接收到。
async function foo() { throw new Error('出错了'); } foo().then(v => console.log('resolve', v), e => console.log('reject', e)); // reject Error: 出错了
Promise对象的状态变化
async
函数返回的 Promise 对象,必须等到内部所有await
命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return
语句或者抛出错误。也就是说,只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。
await命令
正常情况下,await
命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
async function f() { // 等同于 // return 123; return await 123; } f().then(v => console.log(v)) // 123
await
命令后面的 Promise 对象如果变为reject
状态,则reject
的参数会被catch
方法的回调函数接收到。
async function f() { await Promise.reject('出错了'); } f() .then(v => console.log(v)) .catch(e => console.log(e)) // 出错了
任何一个await
语句后面的 Promise 对象变为reject
状态,那么整个async
函数都会中断执行。
async function f() { await Promise.reject('出错了'); await Promise.resolve('hello world'); // 不会执行 }
有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await
放在try...catch
结构里面,这样不管这个异步操作是否成功,第二个await
都会执行。
async function fn() { try { await Promise.reject('出错了'); } catch (erro) {} return await Promise.resolve('hello world'); } fn().then(v => console.log(v)); // hello world
另一种方法是await
后面的 Promise 对象再跟一个catch
方法,处理前面可能出现的错误。
async function fn() { await Promise.reject('出错了') .catch(e => console.log(e)) return await Promise.resolve('hello world'); } fn().then(v => console.log(v)); // 出错了 // hello world
错误处理
如果await
后面的异步操作出错,那么等同于async
函数返回的 Promise 对象被reject
。
async function fn() { await new Promise((resolve, reject) => { throw new Error('出错了'); }); return await ('hello world'); // 前面出错后面不会执行 } fn() .then(v => console.log(v)) .catch(e => console.log(e)); // 出错了
防止出错的方法,也是将其放在try...catch
代码块之中。
async function fn() { try { await new Promise((resolve, reject) => { throw new Error('出错了'); }); } catch (error) {} // await new Promise((resolve, reject) => { // throw new Error('出错了'); // }).catch(e => console.log(e)); return await ('hello world'); // 前面代码放入try...catch中处理不会影响后面执行 } fn() .then(v => console.log(v)) .catch(e => console.log(e)); // hello world
如果有多个await
命令,可以统一放在try...catch
结构中。
async function main() { try { const val1 = await firstStep(); const val2 = await secondStep(val1); const val3 = await thirdStep(val1, val2); console.log('Final: ', val3); } catch (err) { console.error(err); } }
使用注意点
第一点,前面已经说过,await
命令后面的Promise
对象,运行结果可能是rejected
,所以最好把await
命令放在try...catch
代码块中。
async function main() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(erro); } } // 另一种写法 async function main() { await somethingThatReturnsAPromise() .catch(e => console.log(e)); }
第二点,多个await
命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
// 下面两行代码,getFoo
和getBar
是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo
完成以后,才会执行getBar
,完全可以让它们同时触发。
let foo = await getFoo(); let bar = await 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;
第三点,await
命令只能用在async
函数之中,如果用在普通函数,就会报错。
async function dbFuc(db) { let docs = [{}, {}, {}]; // 报错 docs.forEach(function (doc) { await db.post(doc); }); }