多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
async/await 是一种特殊的语法,能够更好的处理promise,可以让你编写基于Promise的代码像同步一样。 ### async async 关键字放在函数之前,使得该函数总是返回一个promise对象。如果代码中显式 return 了一个值,那么函数的返回值将会被自动包装成resolved状态下的promise对象。 例如: ~~~js async function fn() { return 1; } fn().then(res => { console.log(res) // 1 }) ~~~ ##### async函数的使用方式 ~~~js // 函数声明式 async function fn() { } // 函数表达式 const fn = async function () { } // ArrowFunc const fn = async () => {} // 对象中定义 let obj = { async fn() {} } ~~~ 当async函数被调用执行的时候,会返回一个Promise的实例。当函数返回值时,promise会执行。如果函数在执行中抛出错误,那么会进入promise的rejected流程。 比如: ~~~js 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。 ~~~js async function foo() { let result = await 'ok' console.log(result); // ok } foo(); ~~~ 1. 当用变量接收await的返回值时,值为promise为resolved状态下的值。 ~~~js const foo = async () => { let result = await Promise.resolve('ok'); console.log(result); // ok } foo(); ~~~ 2. 函数执行中,遇到await,会首先返回promise,状态为pending,并且下面的代码会停止执行,待awiat后面的Promise执行完毕后才继续执行下面的代码,直到函数体中的代码全部执行完毕后,函数返回的promise状态才变成resolved。 ~~~js 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()); ~~~ 输出顺序: ![](//upload-images.jianshu.io/upload_images/5256541-6b676362c10c14a3.png?imageMogr2/auto-orient/strip|imageView2/2/w/389/format/webp) Markdown 首先打印出pending状态的promise对象。 等到await后面的异步函数执行完后,才依次打印出ok、last,同时Promise的状态成为resolved。 ![](//upload-images.jianshu.io/upload_images/5256541-8cb6f6e09093d7c8.png?imageMogr2/auto-orient/strip|imageView2/2/w/462/format/webp) Markdown 由此可以看出,当在async函数中运行,当遇到await关键字时,会等待关键字后面的函数执行完毕才运行后面的代码。当所有的执行完后函数返回的Promise对象的状态才变成resolved。 ### 异常处理 我们知道在promise中是通过catch来捕获异常的。但是在async中则使用try/catch来捕获异常。 1. 如果await后面的 promise 正常resolve,await promise便会返回结果。但是在reject的情况下,便会抛出异常,并且这种异常需要用try/catch来捕获,否则会导致进程崩溃。 ~~~js const baz = () => { return Promise.reject('Oops'); } const foo = async () => { try { await baz(); } catch(e) { // e 为 baz()返回的promise对象中 reject出来的值 console.log(e); // Oops } } foo(); ~~~ 2. 如果try中有多个await,其中一个await后面的promise为reject,那么在catch中会抛出第一个异常。 如下: ~~~js // 异步操作,返回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状态时,只能捕获第一个异常。 3. 以下这种方式都会捕获到。由于是同步的效果,所以第二次捕获的异常是第一次捕获1s后。 ~~~js 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 ~~~ 4. 如果await后面的promise中抛出异常,那么等同于async函数返回的 Promise 对象被reject。如下: ~~~js 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 ~~~ 5. try里边的promise中的异常不会在catch中捕获,因为异常发生在promise中,只能通过promise的catch()捕获。 ~~~js 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,那么会按照顺序一个个的执行。 ~~~js 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。 2. 并行 如果有多个await,那么先将await后面的异步函数的返回值保存变量中。 ~~~js 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对象,值为数组。 3. 循环中的串行 ~~~js 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 4. 循环中串行改并行 ~~~js 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是最最基本的。 0人点赞 [ECMAScript Next](/nb/16084136) 作者:一萧烟雨任平生 链接:https://www.jianshu.com/p/fd3d571e38db 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。