上一节说,JavaScript异步操作需要通过Promise实现,一个Promise对象在操作网络时是异步的,等到返回后再调用回调函数,执行正确就调用then()
,执行错误就调用catch()
。
虽然异步实现了,不会让用户感觉到页面“卡住”了,但是一堆then()
、catch()
写起来麻烦看起来也乱。
有没有更简单的写法?
一、async函数
可以用关键字async
配合await
调用Promise,实现异步操作,但代码却和同步写法类似:
async function get(url) {let resp = await fetch(url);return resp.json();
}
Fetch API简介
使用async function
可以定义一个异步函数,异步函数和Promise可以看作是等价的。
在async function内部,用await
调用另一个异步函数,写起来和同步代码没啥区别,但执行起来是异步的。
也就是说:
let resp = await fetch(url);
自动实现了异步调用,它和下面的Promise代码等价:
let promise = fetch(url);
promise.then((resp) => {// 拿到resp
})
如果我们要实现catch()
怎么办?用Promise的写法如下:
let promise = fetch(url);
promise.then((resp) => {// 拿到resp
}).catch(e => {// 出错了
});
用await
调用时,直接用传统的try{ ... } catch
:
async function get(url) {try {let resp = await fetch(url);return resp.json();} catch (e) {// 出错了}
}
用async定义异步函数,用await调用异步函数,写起来和同步代码差不多,但可读性大大提高。
需要特别注意的是,await
调用必须在async function中,不能在传统的同步代码中调用。
那么问题来了,一个同步function怎么调用async function呢?
首先,普通function直接用await调用异步函数将报错:
async function get(url) {let resp = await fetch(url);return resp.json();
}function doGet() {let data = await get('/api/categories');console.log(data);
}doGet();
如果把await
去掉,调用实际上发生了,但我们拿不到结果,因为我们拿到的并不是异步结果,而是一个Promise对象:
async function get(url) {let resp = await fetch(url);return resp.json();
}function doGet() {let promise = get('/api/categories');console.log(promise);
}doGet();// [object Promise]
异步函数总是返回一个Promise对象。如果函数体中没有显式地返回任何值,或者返回了一个非Promise值,那么这个值会被包装成一个已解决的Promise返回。
async function resolveWithValue() { return 'Hello, World!'; // 返回一个已解决的Promise,其值为'Hello, World!' } async function resolveWithUndefined() { // 没有返回值,但会隐式地返回一个已解决的Promise,其值为undefined }
因此,在普通function中调用async function,不能使用await,但可以直接调用async function拿到Promise对象,后面加上then()
和catch()
就可以拿到结果或错误了:
async function get(url) {let resp = await fetch(url);return resp.json();
}function doGet() {let promise = get('/api/categories');promise.then(data => {// 拿到datadocument.getElementById('test-response-text').value = JSON.stringify(data);});
}doGet();
因此,定义异步任务时,使用async function比Promise简单,调用异步任务时,使用await比Promise简单,捕获错误时,按传统的try...catch
写法,也比Promise简单。只要浏览器支持,完全可以用async
简洁地实现异步操作。
值得注意的是,虽然await
使异步代码看起来像同步代码一样简单,但它并没有改变JavaScript的单线程和非阻塞特性。
当await
一个Promise时,JavaScript引擎会暂停当前async
函数的执行,直到Promise解决或拒绝,然后继续执行后续的代码。在这个过程中,JavaScript引擎可以处理其他任务,比如事件监听、其他Promise的解决等,因此它仍然是非阻塞的。