1. 基于Promise的async/await
async/await
缺一不可,他们的出生是为Promise
服务的。可以说async/await
是Promise
的进化版。
为什么要有async/await
缺一不可,他们的出生是为Promise服务的。可以说async/await是Promise的进化版。**存在呢?
async/await
的目的是简化使用多个 promise
时的同步行为,并对一组 Promise
执行某些操作。为了解决大量复杂不易读的Promise
异步的问题,才出现的改良版。
那么先说一下async
:
async function process() {}
上面可以看出,async
必须声明的是一个function,不要去声明别的,要是那样await
就会报错。
这样声明也是错的!
const async demo = function () {} // 错误
必须紧跟着function
。接下来说一下await
。
上面说到必须是个函数(function),那么await
就必须是在这个async
声明的函数内部使用,否则就会报错。
就算你这样写,也是错的。
let data = 'data'
demo = async function () {
const test = function () {
await data
}
}
必须是直系(作用域链不能隔代),这样会报错:Uncaught SyntaxError: await is only valid in async function。
2. async的本质
async
声明的函数的返回本质上是一个Promise
。
什么意思呢?就是说你只要声明了这个函数是async
,那么内部不管你怎么处理,它的返回肯定是个Promise
。
看下列例子:
(async function () {
return '我是Promise'
})()
// 返回是Promise
//Promise {<resolved>: "我是Promise"}
你会发现返回是这个:Promise {<resolved>: "我是Promise"}。
自动解析成 Promise.resolve('我是Promise');
等同于:
(async function () {
return Promise.resolve('我是Promise');
})()
所以你想像一般function
的返回那样,拿到返回值,原来的思维要改改了!你可以这样拿到返回值:
const demo = async function () {
return Promise.resolve('我是Promise');
// 等同于 return '我是Promise'
// 等同于 return new Promise((resolve,reject)=>{ resolve('我是Promise') })
}
demo().then(result=>{
console.log(result) // 这里拿到返回值
})
上述三种写法都行,要看注释细节都写在里面了!!像对待Promise一样去对待async的返回值!!!
3. await的本质与例子
await
的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖。
举例:
const demo = async ()=>{
let result = await new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('我延迟了一秒')
}, 1000)
});
console.log('我由于上面的程序还没执行完,先不执行“等待一会”');
}
// demo的返回当做Promise
demo().then(result=>{
console.log('输出',result);
})
await顾名思义就是等待一会,只要await
声明的函数还没有返回,**那么下面的程序是不会去执行的!!!**这就是字面意义的等待一会(等待返回再去执行)。
那么你到这测试一下,你会发现输出是这个:输出 undefined
。这是为什么呢?
你在`demo`函数里面都没声明返回,哪来的`then`?所以正确写法是这样:
const demo = async ()=>{
let result = await new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('我延迟了一秒')
}, 1000)
});
console.log('我由于上面的程序还没执行完,先不执行“等待一会”');
return result;
}
// demo的返回当做Promise
demo().then(result=>{
console.log('输出',result); // 输出 我延迟了一秒
})
推荐的写法是带上then
,规范一点,当然你没有返回也是没问题的,demo
会照常执行。下面这种写法是不带返回值的写法:
const demo = async ()=>{
let result = await new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('我延迟了一秒')
}, 1000)
});
console.log('我由于上面的程序还没执行完,先不执行“等待一会”');
}
demo();
所以可以发现,只要你用await
声明的异步返回,是必须“等待”到有返回值的时候,代码才继续执行下去。
那事实是这样吗?你可以跑一下这段代码:
const demo = async ()=>{
let result = await setTimeout(()=>{
console.log('我延迟了一秒');
}, 1000)
console.log('我由于上面的程序还没执行完,先不执行“等待一会”');
return result
}
demo().then(result=>{
console.log('输出',result);
})
你会发现,输出是这样的:
由于上面的程序还没执行完,先不执行“等待一会”
输出 1
我延迟了一秒
奇怪,并没有await
,setTimeout
是异步,问题在哪?问题就在于setTimeout
这是个异步,但是不是Promise
!起不到“等待一会”的作用。
所以更准确的说法应该是用await声明的Promise异步返回,必须“等待”到有返回值的时候,代码才继续执行下去。
请记住await是在等待一个Promise的异步返回
当然这种等待的效果只存在于“异步”的情况,await
可以用于声明一般情况下的传值吗?
事实是当然可以:
const demo = async ()=>{
let message = '我是声明值'
let result = await message;
console.log(result);
console.log('我由于上面的程序还没执行完,先不执行“等待一会”');
return result
}
demo().then(result=>{
console.log('输出',result);
})
输出:
我是声明值
我由于上面的程序还没执行完,先不执行“等待一会”
输出 我是声明值
这里只要注意一点:then
的执行总是最后的。
4. async/await 优势实战
现在我们看一下实战:
const setDelay = (millisecond) => {
return new Promise((resolve, reject)=>{
if (typeof millisecond != 'number') reject(new Error('参数必须是number类型'));
setTimeout(()=> {
resolve(`我延迟了${millisecond}毫秒后输出的`)
}, millisecond)
})
}
const setDelaySecond = (seconds) => {
return new Promise((resolve, reject)=>{
if (typeof seconds != 'number' || seconds > 10) reject(new Error('参数必须是number类型,并且小于等于10'));
setTimeout(()=> {
resolve(`我延迟了${seconds}秒后输出的,注意单位是秒`)
}, seconds * 1000)
})
}
比如上面两个延时函数(写在上面),比如我想先延时1秒,在延迟2秒,再延时1秒,最后输出“完成”,这个过程,如果用then
的写法,大概是这样(嵌套地狱写法):
setDelay(1000)
.then(result=>{
console.log(result);
return setDelaySecond(2)
})
.then(result=>{
console.log(result);
return setDelay(1000)
})
.then(result=>{
console.log(result);
console.log('完成')
})
.catch(err=>{
console.log(err);
})
乍一看是不是挺繁琐的?如果逻辑多了估计看得更累,现在我们来试一下async/await
(async ()=>{
const result = await setDelay(1000);
console.log(result);
console.log(await setDelaySecond(2));
console.log(await setDelay(1000));
console.log('完成了');
})()
是不是没有冗余的长长的链式代码,语义化也非常清楚,非常舒服,那么你看到这里,一定还发现了,上面的catch
我们是不是没有在async
中实现?接下去我们就分析一下async/await
如何处理错误?
5. async/await错误处理
因为async
函数返回的是一个Promise
,所以我们可以在外面catch
住错误。
const demo = async ()=>{
const result = await setDelay(1000);
console.log(result);
console.log(await setDelaySecond(2));
console.log(await setDelay(1000));
console.log('完成了');
}
demo().catch(err=>{
console.log(err);
})
在async
函数的catch
中捕获错误,当做一个Pormise
处理,同时你不想用这种方法,可以使用try...catch
语句
(async ()=>{
try{
const result = await setDelay(1000);
console.log(result);
console.log(await setDelaySecond(2));
console.log(await setDelay(1000));
console.log('完成了');
} catch (e) {
console.log(e); // 这里捕获错误
}
})()
当然这时候就不需要在外面catch
了。
通常我们的try...catch
数量不会太多,几个最多了,如果太多了,说明你的代码肯定需要重构了,一定没有写得非常好。还有一点就是try...catch
通常只用在需要的时候,有时候不需要catch
错误的地方就可以不写。
try...catch
好像只能包裹代码块,如果我需要拆分开分别处理,不想因为一个的错误就整个process都crash掉了,那么难道要写一堆try...catch
吗?下面有一种很好的解决方案,仅供参考:
await
后面跟着的肯定是一个Promise
,那是不是可以这样写?
(async ()=>{
const result = await setDelay(1000).catch(err=>{
console.log(err)
});
console.log(result);
const result1 = await setDelaySecond(12).catch(err=>{
console.log(err)
})
console.log(result1);
console.log(await setDelay(1000));
console.log('完成了');
})()
这样输出:
我延迟了1000毫秒后输出的
Error: 参数必须是number类型,并且小于等于10
at Promise (test4.html:19)
at new Promise (<anonymous>)
at setDelaySecond (test4.html:18)
at test4.html:56
undefined
我延迟了1000毫秒后输出的
完成了
是不是就算有错误,也不会影响后续的操作,但是这样写得别扭await
又跟着catch
。那么可以改进一下,封装一下提取错误的代码函数:
// to function
function to(promise) {
return promise.then(data => {
return [null, data];
})
.catch(err => [err]); // es6的返回写法
}
返回的是一个数组,第一个是错误,第二个是异步结果,使用如下:
(async ()=>{
// es6的写法,返回一个数组(你可以改回es5的写法觉得不习惯的话),第一个是错误信息,第二个是then的异步返回数据,这里要注意一下重复变量声明可能导致问题(这里举例是全局,如果用let,const,请换变量名)。
[err, result] = await to(setDelay(1000))
// 如果err存在就是有错,不想继续执行就抛出错误
if (err) throw new Error('出现错误,同时我不想执行了');
console.log(result);
[err, result1] = await to(setDelaySecond(12))
// 还想执行就不要抛出错误
if (err) console.log('出现错误,同时我想继续执行', err);
console.log(result1);
console.log(await setDelay(1000));
console.log('完成了');
})()
6. async/await的中断(终止程序)
首先我们要明确的是,Promise
本身是无法中止的,Promise****本身只是一个状态机,存储三个状态(pending,resolved,rejected),一旦发出请求了,必须闭环,无法取消,之前处于pending状态只是一个挂起请求的状态,并不是取消,一般不会让这种情况发生,只是用来临时中止链式的进行。
中断(终止)的本质在链式中只是挂起,并不是本质的取消Promise
请求,那样是做不到的,Promise
也没有cancel
的状态。
不同于Promise
的链式写法,写在async/await
中想要中断程序就很简单了,因为语义化非常明显,其实就和一般的function
写法一样,想要中断的时候,直接return
一个值就行,null
,空,false
都是可以的。看例子:
let count = 6;
const demo = async ()=>{
const result = await setDelay(1000);
console.log(result);
const result1 = await setDelaySecond(count);
console.log(result1);
if (count > 5) {
return '我退出了,下面的不进行了';
// return;
// return false; // 这些写法都可以
// return null;
}
console.log(await setDelay(1000));
console.log('完成了');
};
demo().then(result=>{
console.log(result);
})
.catch(err=>{
console.log(err);
})
实质就是直接return
返回了一个Promise
,相当于return Promise.resolve('我退出了下面不进行了')
,当然你也可以返回一个“拒绝”:return Promise.reject(new Error('拒绝'))
那么就会进到错误信息里去。
async
函数实质就是返回一个Promise
!