文章目录
其他相关传送门
async/await 是 JavaScript 中用于处理异步操作的一对关键字。
它们提供了一种更直观、更易于理解的方式来编写异步代码,使异步代码看起来更像同步代码。
1. async
⚡️async 函数总是返回一个 Promise
有return
如果async函数中有return语句,并返回了一个值,那么这个值会被Promise.resolve包装,成为返回的Promise对象的解析值。
async function successfulAsyncFunction() {
return 'Success!'; // 返回一个 promise 对象,相当于 return Promise.resolve('Success')
}
// 使用 .then() 方法来处理 Promise
successfulAsyncFunction().then(result => {
console.log(result); // 输出 'Success!'
console.log(typeof result); // 输出 'string',因为 result 是解析后的字符串
});
即使你写的是 return 'Success!';
,当你调用 successfulAsyncFunction()
时,你得到的是一个 Promise 对象,而不是直接得到字符串 'Success!'
。你需要使用 .then()
方法或者 await 关键字来访问这个 Promise 解析后的值。
.
没有return
如果async函数中没有return语句,或者return后面没有跟任何值(即隐式返回undefined),那么返回的Promise对象会解析为undefined。
async function asyncFunctionWithoutReturn() {
// 没有 return 语句,将隐式返回 undefined
}
asyncFunctionWithoutReturn().then(result => {
console.log(result); // 输出 undefined
});
虽然看起来没有返回值,但实际上它返回了一个解析为 undefined 的 Promise
.
抛出错误或异常
如果async函数中抛出了一个错误或异常,那么返回的Promise对象会被拒绝(rejected),并且拒绝的原因就是那个错误或异常。
async function failingAsyncFunction() {
throw new Error('Failure!');
}
failingAsyncFunction().catch(error => {
console.error(error); // 输出 'Failure!'
});
.
执行顺序
async function asyncFn() {
console.log('这里是同步');
return '我后执行'; // 返回一个 promise 对象,相当于 return Promise.resolve('我后执行')
}
console.log(asyncFn()); // Promise {<fulfilled>: "我后执行"}
asyncFn().then(result => {
console.log(result+',这里是异步');
})
console.log('我先执行');
// 这里是同步
// 我先执行
// 我后执行,这里是异步
上面的执行结果是先打印出'我先执行'
,虽然是上面asyncFn()
先执行,但是已经被定义异步函数了,不会影响后续函数的执行。
.
async函数就是Generator 函数的语法糖
相较于 Generator,async
函数的改进在于下面四点:
- 内置执行器:
Generator
函数的执行必须依靠执行器,而async
函数自带执行器,调用方式跟普通函数的调用一样 - 更好的语义:
async
和await
相较于*
和yield
更加语义化 - 更广的适用性:
co
模块约定,yield
命令后面只能是Thunk
函数或Promise
对象。而async
函数的await
命令后面则可以是Promise
或者 原始类型的值(Number
,string
,boolean
,但这时等同于同步操作) - 返回值是 Promise:
async
函数返回值是Promise
对象,比Generator
函数返回的Iterator
对象方便,可以直接使用then()
方法进行调用
在处理异步操作时,Generator函数结合Promise可以有效地避免回调地狱的问题。
例如,假设我们有一系列的异步操作需要依次执行,每个操作都依赖于前一个操作的结果:
function fetchData1() {
return new Promise(resolve => {
setTimeout(() => resolve('Data 1'), 1000);
});
}
function fetchData2(data1) {
return new Promise(resolve => {
setTimeout(() => resolve(`Data 2 based on ${data1}`), 1000);
});
}
用Generator 函数:
function* asyncFetch() {
const data1 = yield fetchData1();
const data2 = yield fetchData2(data1);
return data2;
}
function runGenerator(gen) {
const iterator = gen();
function step(result) {
if (result.done) return result.value;
result.value.then(value => {
step(iterator.next(value));
}).catch(error => {
console.error(error);
});
}
return step(iterator.next());
}
runGenerator(asyncFetch()).then(data2 => {
console.log(data2); // 输出 "Data 2 based on Data 1"
});
将上面的Generator函数和Promise结合的代码转换为使用async和await的写法可以使代码更简洁且易于理解。
用async和await函数:
async function asyncFetch() {
const data1 = await fetchData1();
const data2 = await fetchData2(data1);
return data2;
}
asyncFetch().then(data2 => {
console.log(data2); // 输出 "Data 2 based on Data 1"
});
.
.
在async
里,如果不将结果return
出去,不管是执行reject
还是resolved
的值都为undefine
即:如果没有return
,则默认return undefine
2. await
await
意思是 async wait
(异步等待)。
⚡️只能用在async函数中
await关键字必须用在async函数内部如果尝试在普通函数中使用await,JavaScript会抛出一个语法错误。
async function asyncFetch() {
const data1 = await fetchData1();
const data2 = await fetchData2(data1);
return data2;
}
.
⚡️等待Promise
await 后面跟随的表达式通常应该是一个 Promise 对象,如果await后面跟随的不是Promise,它会被自动包装成一个已经解析的Promise。 await关键字只能用于等待Promise对象的解析。
async function asyncFetch() {
const data1 = await new Promise(resolve => {
setTimeout(() => resolve('Data 1'), 1000);
}); ;
return data1;
}
.
⚡️暂停执行并阻塞后面的代码
当在async函数内部遇到await关键字时:
- 会暂停当前函数的执行
- 阻塞await 后面的代码
直到等待的Promise对象解析完成(无论是解析为值还是拒绝为错误)。
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function demoFunction() {
console.log('Taking a break...');
await delay(2000); // 会使得 demoFunction 暂停执行,直到 delay 函数中的 Promise 在 2 秒后解析。
console.log('Two seconds later'); // 会被上面的await阻塞2秒
}
demoFunction();
.
⚡️按顺序执行
如果内部遇到多个await关键字,它们会按照在 async 函数中出现的顺序依次等待每个 Promise 的完成。每个 await 都会阻塞后续代码的执行,直到它等待的异步操作完成。
这意味着第一个 await 表达式后面的代码(包括下一个 await 表达式)只有在第一个 await 的 Promise 完成之后才会执行。
async function asyncFetch() {
const data1 = await fetchData1(); // fetchData1 完成后才会执行下一个await
const data2 = await fetchData2(data1); // 按顺序依次执行
return data2;
}
.
⚡️返回解析值
一旦Promise对象解析完成,await会返回这个Promise解析的值(如果Promise被解析的话)。
如果Promise被拒绝,await表达式会抛出一个异常,可以使用try…catch语句来捕获和处理这个异常。
function dice(val) {
return new Promise((resolve, reject) => {
setTimeout(() => {
let sino = parseInt(Math.random() * 6 + 1);
if (sino > 3) {
resolve(sino)
} else {
reject(sino);
}
}, 1000);
})
}
async function test() {
// try...catch 可捕获异常,代替了 Promise 的 catch
try {
//把await及获取它的值的操作放在try里
let n = await dice(); // await 相当于 Promise 的 then await会返回这个Promise解析的值
return n
} catch (error) {
//失败的操作放在catch里 相当于 Promise 的 catch
throw error; // 重新抛出错误,以便在外部处理
}
}
test().then(result => {
console.log('赌大:', result); // 输出:处理后的数据:这是从异步操作获取的数据
}).catch(error => {
console.log('赌小:', error);
});
.
错误处理
由于await会抛出Promise拒绝时产生的错误,因此建议将await放在try...catch
块中,以便妥善处理可能出现的错误。
.
不阻塞主线程
尽管await会让async函数暂停执行,但它并不会阻塞主线程。(Promise的回调属于微任务)
在等待Promise解析的过程中,主线程可以继续执行其他任务,从而实现非阻塞的异步操作。
.
改善异步流程控制
使用await可以简化异步流程的控制,使代码更加线性化和易于理解,避免了传统回调函数或Promise链导致的“回调地狱”问题。
.
任何 async
函数都会默认返回 promise
对象,并且这个 promise
解析的值都将会是这个函数的返回值,而 async
函数必须等到内部所有的 await
命令的 Promise
对象执行完,才会发生状态改变。
就是说,必须等所有await
函数执行完毕后,才会告诉promise
我成功了还是失败了,执行then
或者catch
3. await 等到之后,做了一件什么事情?
await 下面所有的代码都是异步
await
等到的结果分2种:
- 不是promise对象
如果不是 promise , await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完,再回到async内部,把这个非promise的对象,作为 await表达式的结果。
async function async1() {
await async2(); // 先执行async2(),await下面所有的代码都是异步
console.log(1); // 异步
}
async function async2() {
console.log(2);
}
console.log(3);
async1();
// 3
// 2
// 1
- 是promise对象
如果是 promise 对象,await 也会阻塞async后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。
function fn() {
return new Promise(resolve => {
console.log(1); // 同步2
resolve();
})
}
async function async1() {
await fn().then(() => { // 先执行fn(),await下面所有的代码都是异步
console.log(2); // 异步1
})
console.log(3); // 异步1的下一个异步
}
console.log(4); // 同步1
async1();
// 4
// 1
// 2
// 3
如果asycn里的代码都是同步的,那么这个函数被调用就会同步执行
async function fn(){
console.log('a'); // 同步1
}
fn();
console.log('b'); // 同步2
//a
//b
async function async1() {
console.log(1); // 同步2
await async2(); // 先执行async2() 再await
console.log(2); // 异步(await下面所有的代码都是异步)
}
async function async2() {
console.log(3); // 同步3
}
console.log(4); // 同步1
async1();
console.log(5); // 同步4
// 4
// 1
// 3
// 5
// 2
async function async1() {
console.log(1);
await async2();
console.log(2);
await async3();
console.log(3);
}
async function async2() {
return new Promise((resolve) => {
console.log(4);
resolve(); // 如果没有resolve(),await async2()后的代码都不会执行,只输出6 1 4 7
})
}
async function async3() {
console.log(5);
}
console.log(6);
async1();
console.log(7);
// 6
// 1
// 4
// 7
// 2
// 5
// 3
4. 使用 async/await 改写 then 链
相比于 Promise
,async/await
能更好地处理 then链
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
使用then的链式调用
:
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
});
}
doIt();
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
使用 async/await
:
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
}
doIt();
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900