JavaScript----async/await和Promis
1. async 和 await 在干什么
async 是“异步”的简写,async 用于申明一个 function 是异步的
await 可以认为是 async wait 的简写。 await 用于等待一个异步方法执行完成。await 只能出现在 async 函数中
1.1. async 起什么作用
async 函数是怎么处理它的返回值的!
写段代码来试试,看它到底会返回什么:
async function testAsync() {
return "hello async";
}
const result = testAsync();
console.log(result);
输出的是一个 Promise 对象。
Promise { 'hello async' }
async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,
- 如果在函数中
return
一个直接量,async 会把这个直接量通过Promise.resolve()
封装成Promise 对象。
Promise.resolve(x)
可以看作是new Promise(resolve => resolve(x))
的简写,可以用于快速封装字面量对象或其他对象,将其封装成Promise 实例
。
async 函数
返回的是一个 Promise 对象
,所以在最外层不能用 await 获取其返回值的情况下,我们当然应该用原来的方式:then()
链来处理这个 Promise 对象
,
testAsync().then(v => {
console.log(v); // 输出 hello async
});
如果async 函数
没有返回值,返回 Promise.resolve(undefined)
。
Promise
的特点——无等待,所以在没有 await
的情况下执行 async 函数
,它会立即执行,返回一个 Promise 对象
,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。
1.2. await 到底在等啥
一般来说,都认为 await 是在等待一个 async 函数完成。不过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是Promise 对象
或者其它值
(没有特殊限定)。
因为 async 函数
返回一个 Promise 对象
,所以await
可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。所以下面这个示例完全可以正确运行
function getSomething() {
return "something";
}
async function testAsync() {
return Promise.resolve("hello async");
}
async function test() {
const v1 = await getSomething();
const v2 = await testAsync();
console.log(v1, v2);
}
test();
1.3. await 等到了要等的,然后呢
await
等到了它要等的东西,一个 Promise 对象
,或者其它值
,然后呢?
-
如果它等到的不是一个
Promise 对象
,那 await 表达式的运算结果就是它等到的东西。 -
如果它等到的是一个
Promise 对象
,await
会阻塞后面的代码,等着Promise 对象 resolve
,然后得到 resolve 的值,作为 await 表达式的运算结果。
async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。
2. async/await 帮我们干了啥
2.1. 作个简单的比较
上面已经说明了async
会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象
,而await
会等待这个 Promise
完成,并将其resolve
的结果返回出来。
现在举例,用 setTimeout
模拟耗时的异步操作,先来看看不用 async/await 会怎么写
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => resolve("long_time_value"), 1000);
});
}
takeLongTime().then(v => {
console.log("got", v);
});
如果改用 async/await
呢,会是这样
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => resolve("long_time_value"), 1000);
});
}
async function test() {
const v = await takeLongTime();
console.log(v);
}
test();
takeLongTime()
没有申明为 async
。实际上,takeLongTime()
本身就是返回的 Promise 对象。
2.2. async/await 的优势在于处理 then 链
单一的Promise 链
并不能发现 async/await
的优势,但是,如果需要处理由多个 Promise
组成的 then
链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。
假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout
来模拟异步操作:
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
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);
}
现在用 Promise
方式来实现这三个步骤的处理
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}`);
console.timeEnd("doIt");
});
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms
输出结果 result
是 step3()
的参数 700 + 200
= 900
。doIt()
顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500
毫秒,和 console.time()/console.timeEnd()
计算的结果一致。
如果用 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}`);
console.timeEnd("doIt");
}
doIt();
结果和之前的 Promise
实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样
2.3. 还有更酷的
现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(m, n) {
console.log(`step2 with ${m} and ${n}`);
return takeLongTime(m + n);
}
function step3(k, m, n) {
console.log(`step3 with ${k}, ${m} and ${n}`);
return takeLongTime(k + m + n);
}
这回先用 async/await
来写:
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time1, time2);
const result = await step3(time1, time2, time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms
如果把它写成 Promise
方式实现:
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => {
return step2(time1, time2)
.then(time3 => [time1, time2, time3]);
})
.then(times => {
const [time1, time2, time3] = times;
return step3(time1, time2, time3);
})
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
那一堆参数处理,就是 Promise 方案的死穴—— 参数传递麻烦。