Promise 链式写法
Promise 的
then链式写法本质上是一直往下传递一个新的Promise,也就是说then在下一步接收的是上一步返回的Promise。
看以下代码:
const setDelay = millisecond => {
return new Promise((resolve, reject) => {
if ('number' != typeof millisecond) reject(new Error('参数必须是number类型'))
setTimeout(() => {
resolve(`我延迟了${ millisecond }毫秒后输出的`)
}, millisecond);
})
}
const setSecond = second => {
if ('number' != typeof second || 10 < second) throw new Error('参数必须是number类型,并且必须小于10')
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`我延迟了${ second }秒后输出的`)
}, second * 1000);
})
}
setDelay(3000)
.then(res => {
console.log(res)
console.log('我进行到第一步')
return setSecond(2)
})
.then(result => {
console.log('现在是第二步')
console.log(result)
}) .catch(err => console.log(err))
// 我延迟了3000毫秒后输出的
// 我进行到第一步
// 现在是第二步
// 我延迟了2秒后输出的
错误处理
setDelay(2000)
.then(result => {
console.log(result)
console.log('我是进行到第一步的')
return setSecond(20)
})
.then(result => {
console.log('我是进行到第二步的')
console.log(result)
}, err => console.log(err))
.then(result => console.log('我还是继续执行的'))
.catch(err => console.log(err))
// 我延迟了2000毫秒后输出的
// 我是进行到第一步的
// 我出错啦,进到这里捕获错误,但是不经过catch了
// 我还是继续执行的
可以看到进到 then 的第二个参数 reject 中去了,而且不再经过 catch 了。
那么我们把 catch 挪上去,写到 then 错误之前:
setDelay(2000)
.then(result => {
console.log(result)
console.log('我是进行到第一步的')
return setSecond(20)
})
.catch(err => console.log(err))
.then(result => {
console.log('我是进行到第二步的')
console.log(result)
}, err => console.log('我出错啦,进到这里捕获错误,但是不经过catch了'))
.then(result => console.log('我还是继续执行的'))
// 我延迟了2000毫秒后输出的
// 我是进行到第一步的
// Error: 参数必须是number类型,并且必须小于10
// 我是进行到第二步的
// undefined
// 我还是继续执行的
可以看到先经过 catch 捕获,后面就没有错误了。
结论
catch写法是针对于整个链式写法的错误而捕获的,而then第二个参数是针对于上一个返回的Promise的。- 两者的优先级别:写在前面的先捕获,后面就没有错误可以捕获了。
- 两者都不是
break,可以继续执行后续的操作而不受影响。 - 链式中的
catch并不是终点,catch完如果有then还会继续往下走。
如何跳出或停止 Promise 链式
-
直接拒绝某一链
setDelay(2000) .then(result => { console.log(result) console.log('我是进行到第一步的') return setSecond(2) }) .then(result => { console.log('我是进行到第二步的') console.log(result) console.log('我在这一层主动跳出循环') return Promise.reject({ isNotErrorException: true, msg: '跳出循环的信息' }) }) .then(res => console.log('我不执行')) .catch(mes => { console.log(mes) console.log('我跳出了') }) // 我延迟了2000毫秒后输出的 // 我是进行到第一步的 // 我是进行到第二步的 // 我延迟了2秒后输出的 // 我在这一层主动跳出循环 // { isNotErrorException: true, msg: '跳出循环的信息' } // 我跳出了但是这样存在一个问题,就是当我们的
catch放在中间,不是末尾,上述方法中止后catch后面的代码会继续执行,而我们又不想执行catch后面的代码,也就是链式的绝对中止。如下:
在
.catch后加上这一段:.then(res => console.log('我不想执行,但是却执行了')),可以看到最后依然会执行这一句,那么该怎么办呢?这时候就要使用第二种方法了。
-
让当前层级一直
pending下去setDelay(2000) .then((result)=>{ console.log(result) console.log('我进行到第一步的'); return setDelaySecond(1) }) .then((result)=>{ console.log(result); console.log('我主动跳出循环了'); // return Promise.reject('跳出循环的信息') // 重点在这 return new Promise(()=>{console.log('后续的不会执行')}) // 这里返回的一个新的Promise,没有resolve和reject,那么会一直处于pending状态,因为没返回啊,那么这种状态就一直保持着,中断了这个Promise }) .then((result)=>{ console.log('我不执行'); }) .catch((mes)=>{ console.dir(mes) console.log('我跳出了'); }) .then((res)=>{ console.log('我也不会执行') })这样就解决了上述,错误跳出而导致无法完全中止
Promise链的问题。但是这可能会导致潜在的内存泄露。
Await、Async
async 的本质
async声明的函数的返回本质上是一个Promise对象
只要声明了这个函数是 async ,那么内部不管你怎么处理,它的返回值肯定是个 Promise 对象。
来看下面的例子:
(async function () {
return '我是Promise'
})()
// 返回的是Promise
// Promise {<resolved>: "我是Promise"}
会自动解析成 Promise.resolve('我是Promise');
等同于:
(async function () {
return Promise.resolve('我是Promise');
})()
await 的本质
await等的是一个Promise的异步返回,如果等待的不是一个Promise,是起不到等待一会儿的作用的。
例如:
const demo = async () => {
let res = await setTimeout(() => {
console.log('我延迟了一秒')
}, 1000)
console.log('我由于上面的程序还没执行完,先不执行,等待一会儿')
return res
}
demo ().then(res => console.log('输出', res))
// 我由于上面的程序还没执行完,先不执行“等待一会”
// 输出 1
// 我延迟了一秒
可以看到,并没有 await ,验证了上面所提到的注意事项。
实战演练
场景1
常规使用 async 、await 时,我们通常将代码块使用 try...catch 来包裹,但是如果我们想拆分开来分别处理,不想因为一个的错误就整个process都crash掉了,那么难道我要写一堆 try...catch 么?
方法一
(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('完成了');
})()
可以看到,就算是有错误,也不会影响后续的操作。但是,这种方法的缺陷是非常不规整,await 后又跟着 catch 。
可以改进一下,封装一个提取错误的函数:
方法二
function to (promise) {
return promise.then(data => {
return [null, data]
})
.catch(err => [err])
}
(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('完成了');
})()
场景2
依次分别延迟1秒输出值,一共5秒。
延迟函数如下:
const setDelay = (millisecond) => {
return new Promise((resolve, reject)=>{
if (typeof millisecond != 'number') reject(new Error('参数必须是number类型'));
setTimeout(()=> {
resolve(`我延迟了${millisecond}毫秒后输出的`)
}, millisecond)
})
}
错误的方法:
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)
})
执行,我们发现这样输出是并行的。也就是说一秒钟一次性输出了3个值。
原因分析:
把
setDelay(1000)这个直接添加到数组的时候,其实就已经执行了,注意你的执行语句(1000)
所以我们可以将 Promise 预先存储在一个数组中,在调用的时候,再去执行。当然你也可以用闭包的方式存储起来,需要调用的时候再执行。
arr = [setDelay, setDelay, 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循环
arr = [setDelay, setDelay, setDelay]
var temp
temp = arr[0](1000)
for (let i = 1; i <= arr.length; i++) {
if (i == arr.length) {
temp.then(result=>{
console.log('完成了');
})
break;
}
temp = temp.then((result)=>{
console.log(result);
return arr[i-1](1000)
});
}
直接使用async/await
(async ()=>{
arr = [timeout(2000), timeout(1000), timeout(1000)]
for (var i=0; i < arr.length; i++) {
result = await arr[i]();
console.log(result);
}
})()