文章目录
Promise知识点汇总
Promise链式写法
定义2个函数,返回Promise:
/**
* 延迟millisecond毫秒后输出
* @param millisecond
* @return {Promise<unknown>}
*/
setDelay = millisecond => {
return new Promise((resolve, reject) => {
if (typeof millisecond != 'number') {
reject(new Error('参数必须是number类型'));
}
setTimeout(() => {
resolve(`我延迟了${millisecond}毫秒后输出的,是第1个函数setDelay`)
}, millisecond)
})
}
/**
* 延迟seconds秒后输出
* @param seconds
* @return {Promise<unknown>}
*/
setDelaySecond = seconds => {
return new Promise((resolve, reject) => {
if (typeof seconds != 'number' || seconds > 10) {
reject(new Error('参数必须是number类型,并且小于等于10'));
}
setTimeout(() => {
resolve(`我延迟了${seconds}秒后输出的,是第2个函数setDelaySecond`)
}, seconds * 1000)
})
}
Promise链式写法:
setTimeout(this.setDelay(2000)
.then((result) => {
console.log(result);
console.log(`显示第1个函数setDelay的result:${result}`);
// return Promise.resolve(3);
return this.setDelaySecond(3);
})
.then((result) => {
console.log('我进行到第二步的');
console.log(result);
console.log(`显示第2个函数setDelaySecond的result:${result}`);
}).catch((err) => {
console.log(err);
}), 5000)
catch和then捕获Promise异常
then
式链式写法的本质其实是一直往下传递返回一个新的Promise
,也就是说then
在下一步接收的是上一步返回的Promise
.
then
在catch
前:错误进入then
的第2个回调函数,不进入catch
。
setDelay(2000)
.then((result)=>{
console.log(result)
console.log('我进行到第一步的');
return setDelaySecond(20)
})
.then((result)=>{
console.log('我进行到第二步的');
console.log(result);
}, (_err)=> {
console.log('我出错啦,进到这里捕获错误,但是不经过catch了');
})
.then((result)=>{
console.log('我还是继续执行的!!!!')
})
.catch((err)=>{
console.log(err);
})
catch
在then
前:错误在catch
捕获,不进入then
的第2个回调函数。
setDelay(2000)
.then((result)=>{
console.log(result)
console.log('我进行到第一步的');
return setDelaySecond(20)
})
.catch((err)=>{ // 挪上去了
console.log(err); // 这里catch到上一个返回Promise的错误
})
.then((result)=>{
console.log('我进行到第二步的');
console.log(result);
}, (_err)=> {
console.log('我出错啦,但是由于catch在我前面,所以错误早就被捕获了,我这没有错误了');
})
.then((result)=>{
console.log('我还是继续执行的!!!!')
})
总结:
catch
写法是针对于整个链式写法的错误捕获的,而then
第二个参数是针对于上一个返回Promise
的。- 两者的优先级:就是看谁在链式写法的前面,在前面的先捕获到错误,后面就没有错误可以捕获了,链式前面的优先级大,而且两者都不是
break
, 可以继续执行后续操作不受影响。
需要注意的是catch
不是链式的终点,如果catch
后还有then
,整个链式表达式还会继续执行下去,简而言之catch
只是捕获链式表达式的错误,不是break
。
主动跳出Promise链式
使用reject可以主动跳出链式,但是后面的catch中的代码还是会继续执行:
this.setDelay(2000)
.then((result) => {
console.log(result)
console.log('我进行到第一步的');
return this.setDelaySecond(1)
})
.then((result) => {
console.log('我进行到第二步的');
console.log(result);
console.log('我主动跳出循环了');
return Promise.reject('跳出循环的信息') // 这里返回一个reject,主动跳出循环了
})
.then((result) => {
console.log(result);
console.log('我不执行');
})
.catch((mes) => {
console.dir(mes); // 跳出循环的信息
console.log('我继续执行');
})
执行顺序:
- 进入第1个
then
,执行setDelaySecond
- 进入第2个
then
,执行Promise.reject()
- 跳过第3个
then
- 执行
catch
中的代码
一般来说,通过添加标识位来控制是否跳出Promise
链式:
return isNotErrorExpection ?
Promise.reject('跳出循环的信息') : Promise.resolve('继续执行');
Promise.all
Promise.all
参数是Promise
数组,输出是resolve
数组,注意Promise.all
中的Promise
是并行执行的,都执行完后把resolve
的值保存在数组中输出。
注意Promise.all
总耗时是长的Promise
执行时间。
Promise.all([this.setDelay(3000), this.setDelaySecond(1)])
.then(result => {
// 输出[
// "我延迟了3000毫秒后输出的,是第1个函数setDelay",
// "我延迟了1秒后输出的,是第2个函数setDelaySecond"
// ]
console.log(result);
}).catch(err => {
console.log(err);
});
async/await
async
async
必须声明的是一个function
(async
必须紧跟function
)
// 正确
async function process() {
}
// 错误
const async demo = function () {}
await
就必须是在这个async
声明的函数内部使用,必须是第1层区域。
let data = 'data'
demo = async function () {
const test = function () {
await data // 在第2层,错误
}
}
async
的本质就是Promise
,下面两种写法一样,demo
是Promise
,通过then
表达式拿到Promise
的resolve
值。
方法1:
// async本质是Promise
async show() {
return ('我是Promise');
}
方法2:
// async本质是Promise
async show() {
// 写法2
return new Promise((resolve, reject) => {
resolve('我是Promise')
});
}
通过then表达式拿到Promise值:
const demo = this.show();
console.log(demo); // Promise {<fulfilled>: "我是Promise"}
demo.then(result => {
console.log(result); // 通过then拿到resolve值
})
总结:像对待Promise
一样去对待async
的返回值,通过then
表达式拿到具体的返回值。
await错误用法
await
顾名思义就是等待一会,只要await
声明的函数还没有返回,那么下面的程序是不会去执行的。需要注意的是,await等待的是Promise
的异步返回!!!看如下代码:
handleClick = () => {
this.demo().then(result => {
console.log('输出', result); // 输出 我延迟了1秒
});
}
// demo的返回值是Promise,推荐带上返回值
demo = async () => {
let result = await setTimeout(() => {
console.log('我延迟了一秒');
}, 1000);
console.log('我由于上面的程序还没执行完,先不执行“等待一会”');
return result;
}
this.demo().then(result => {
console.log('输出', result); // 输出 我延迟了1秒
});
执行结果为:
可以看到await
没有对setTimeout
这个异步方法起作用。
await正确用法
await
是在等待一个Promise
的异步返回。
handleClick = () => {
this.demo().then(result => {
console.log('输出', result); //
});
}
// demo的返回值是Promise,推荐带上返回值
demo = async () => {
let result = await new Promise((resolve, reject) => {
resolve('我是声明值');
});
console.log(result);
console.log('我由于上面的程序还没执行完,先不执行“等待一会”');
return result;
}
输出结果:可以看到await
等Promise
执行完再执行下一步,注意then
的执行总是最后的。
使用async/await 避免Promise链式写法
目标:顺序执行,先延时1秒,在延迟2秒,再延时1秒,最后输出“完成”。
之前2个延时函数:
/**
* 延迟millisecond毫秒后输出
* @param millisecond
* @return {Promise<unknown>}
*/
setDelay = millisecond => {
return new Promise((resolve, reject) => {
if (typeof millisecond != 'number') {
reject(new Error('参数必须是number类型'));
}
setTimeout(() => {
resolve(`我延迟了${millisecond}毫秒后输出的,是第1个函数setDelay`)
}, millisecond)
})
}
/**
* 延迟seconds秒后输出
* @param seconds
* @return {Promise<unknown>}
*/
setDelaySecond = seconds => {
return new Promise((resolve, reject) => {
if (typeof seconds != 'number' || seconds > 10) {
reject(new Error('参数必须是number类型,并且小于等于10'));
}
setTimeout(() => {
resolve(`我延迟了${seconds}秒后输出的,是第2个函数setDelaySecond`)
}, seconds * 1000)
})
}
使用Promise
实现,代码如下:通过button
触发。
handleClick = () => {
this.setDelay(1000).then(result => {
console.log(result);
return this.setDelaySecond(2);
}).then(result => {
console.log(result);
return this.setDelay(1000)
}).then(result => {
console.log(result);
console.log('完成');
}).catch(err => {
console.log(err);
})
}
使用async/await
来实现同样的效果:
// 使用async/await循环执行
handleClick = () => {
this.demo().then(result => {
console.log(result);
});
}
demo = async () => {
const result = await this.setDelay(1000);
console.log(result);
console.log(await this.setDelaySecond(2));
console.log(await this.setDelay(1000));
return ('执行完成');
}
执行效果:
async/await的中断
Promise
本身是无法中止的,Promise
本身只是一个状态机,存储三个状态(pending
,resolved
,rejected
),一旦发出请求了,必须闭环,无法取消
不同于Promise
的链式写法,在async/await
中想要中断程序就很简单了,想要中断的时候,直接return一个值就行,null,空,false都是可以的。
// 使用async/await中断
handleClick = () => {
this.demo().then(result => {
console.log(result); // 中断
});
}
demo = async () => {
let count = 6;
const result = await this.setDelay(1000);
console.log(result);
if (count > 5) {
return '中断';
}
console.log(await this.setDelaySecond(2)); // 此句不执行
console.log(await this.setDelay(1000)); // 此句不执行
return '执行完毕'; // 此句不执行
}
实质就是直接return
返回了一个Promise
,相当于return Promise.resolve('我退出了下面不进行了')
.
执行效果:
最后牢记:async函数实质就是返回一个Promise
。
实战中异步需要注意的地方
Promise获取数据(串行)之then写法注意
我们需要实现一个依次分别延迟1秒输出值,一共3次的程序。单次的方法如下:
setDelay = millisecond => {
return new Promise((resolve, reject) => {
if (typeof millisecond != 'number') {
reject(new Error('参数必须是number类型'));
}
setTimeout(() => {
resolve(`我延迟了${millisecond}毫秒后输出的,是第1个函数setDelay`)
}, millisecond)
})
}
注意不能使用Promise.all
方法,因为是并行的。
下面这种写法也是并行的:同时输出。
arr = [setDelay(1000), setDelay(1000), setDelay(1000)]
arr[0]
.then(result=>{
console.log(result)
return arr[1]
})
.then(result=>{
console.log(result)
return arr[2]
})
.then(result=>{
console.log(result)
})
因为setDelay
是Promise
,setDelay(1000)
放入数组中的时候已经执行了,也就是说数组里面保存的每个Promise
状态都是resolve
完成的状态了,那么你后面链式调用直接return arr[1]
其实没有去请求,只是立即返回了一个resolve
的状态。所以你会发现程序是相当于并行的,没有依次顺序调用。
正确的写法:
// 每隔1秒输出
handleClick = () => {
let arr = [this.setDelay, this.setDelay, this.setDelay]
arr[0](1000)
.then(result => {
console.log(result)
return arr[1](1000)
})
.then(result => {
console.log(result)
return arr[2](1000)
})
.then(result => {
console.log(result)
})
}
上述相当于把Promise
预先存储在一个数组中,在你需要调用的时候,再去执行。
Promise循环获取数据(串行)之for循环
实现代码:
// 每隔2秒输出
handleClick = () => {
let arr = [this.timeout(2000, 1), this.timeout(2000, 2), this.timeout(2000, 3)];
this.syncPromise(arr).then(result => {
console.log(result);
console.log('完成了');
});
}
syncPromise = arr => {
const _syncLoop = function (count) {
if (count === arr.length - 1) { // 是最后一个就直接return
return arr[count]();
}
return arr[count]().then((result) => {
console.log(result);
return _syncLoop(count + 1) // 递归调用数组下标
});
}
return _syncLoop(0);
}
// 闭包Promise,timeout是方法不是Promise
timeout = (millisecond, count) => {
return () => {
// setDelay是Promise
return this.setDelay(millisecond, count);
}
}
输出结果:
首先你需要闭包你的Promise
程序。
// 闭包Promise,timeout是方法不是Promise
timeout = (millisecond, count) => {
return () => {
// setDelay是Promise
return this.setDelay(millisecond, count);
}
}
如果不闭包会导致什么后果呢?不闭包的话,你传入的参数值后,你的Promise
会马上执行,导致状态改变,如果用闭包实现的话,你的Promise
会一直保存着,等到你需要调用的时候再使用。而且最大的优点是可以预先传入你需要的参数。
总结:使用Promise实现循环输出的功能太繁琐。
async/await循环获取数据(串行)之for循环
实现过程非常简洁:注意arr[i]()
才是方法
// 每隔2秒输出
handleClick = () => {
let arr = [this.timeout(2000, 1), this.timeout(2000, 2), this.timeout(2000, 3)];
this.demo(arr).then(result => {
console.log(result);
});
}
demo = async (arr) => {
for (let i = 0; i < arr.length; i += 1) {
const result = await arr[i]();
console.log(result);
}
return ('执行结束');
}
// 闭包Promise,timeout是方法不是Promise
timeout = (millisecond, count) => {
return () => {
// setDelay是Promise
return this.setDelay(millisecond, count);
}
}
输出结果:
鸣谢和感想
之前看阮一峰大神关于Promise
的文章,理解的不是很深刻,偶尔中看到Leon大神的文章《异步Promise及Async/Await可能最完整入门攻略》,按照文章完整的撸了一遍代码,对Promise
和async/await
语法糖的理解更加深刻了。
感谢阮一峰大神和Leon大神,本文所有代码采用React + Ant Design Pro实现。
对前端的学习,最近有一些感悟:学习的过程唯有阅读、理解、实践、反馈、记录,才能真正掌握一个知识点;前端的学习就是一个个✨一样的知识点汇聚成的银河,在探索星空的过程中,唯有脚踏实力、砥砺前行,才能获得一点点的进步。