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!










