JavaScript 系列之异步编程(二)

359 阅读3分钟

这是我参与8月更文挑战的第27天,活动详情查看:8月更文挑战

二、Promise 进阶

2.1 Promise 相互依赖

const setDelay = (millisecond) => {
  return new Promise((resolve, reject) => {
    if (typeof millisecond != 'number') {
      reject(new Error('参数必须是numbe类型'));
    }
    
    setTimeout(() => {
      resolve(`我延迟了${millisecond}毫秒后输出的`);
    }, millisecond);
  });
}
const setDelaySecond = (seconds) => {
  return new Promise((resolve, reject)=>{
    if (typeof seconds != 'number' || seconds > 10) {
      reject(new Error('参数必须是number类型,并且于等于10'));
    }

    setTimeout(()=> {
      console.log(`先是setDelaySeconds函数输出,延迟了${seconds}秒,一共需要延迟${seconds+2}秒`);
      
      resolve(setDelay(2000)) // 这里依赖上一个Promise
    }, seconds * 1000);
  });
}
setDelaySecond(3).then((result)=>{
  console.log(result);
  // “先是setDelaySeconds输出,延迟了2秒,一共需要延迟5秒”
  // “我延迟了2000毫秒后输出的”
}).catch((err)=>{
  console.log(err);
})

这里做到了依次执行的目的,但如果不想耦合性这么高,想先执行 setDelay 函数再执行 setDelaySecond 也可以。

2.2 Promise 链式写法

先改写一下 setDelaySecond,拒绝依赖,降低耦合性:

const setDelaySecond = (seconds) => {
  return new Promise((resolve, reject)=>{
    if (typeof seconds != 'number' || seconds > 10) {
      reject(new Error('参数必须是number类型,并且于等于10'));
    }
    
    setTimeout(()=> {
      resolve(`我延迟了${seconds}秒后输出的,是第二个函数`);
    }, seconds * 1000);
  })
}

先执行 setDelay 在执行 setDelaySecond,只需要在第一个 then 的结果中返回下一个 Promise 就可以一直链式写下去了,相当于依次执行:

setDelay(2000)
.then((result)=>{
  console.log(result)
  console.log('我进行到第一步的');
  return setDelaySecond(3)
})
.then((result)=>{
  console.log('我进行到第二步的');
  console.log(result);
}).catch((err)=>{
  console.log(err);
})
// 我延迟了2000毫秒后输出的
// 我进行到第一步的
// 我进行到第二步的
// 我延迟了3秒后输出的,是第二个函数

Promise 链式写法的本质其实是一直往下传递返回一个新的 Promise,也就是说 then 在下一步接收的是上一步返回的 Promise。

2.3 Promise 错误处理

then 的其实有 2 个回调参数:

  • 第一个回调是 resolve 的回调,也就是第一个参数用得最多,拿到的是上一步的 Promise 成功 resolve 的值。
  • 第二个回调是 reject 的回调,用的不多,通常是拿到上一个的错误,那么这个错误处理和 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);
})

可以看到输出结果是:进到了 then 的第二个参数(reject)中去了,而且不再经过 catch 了

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 的捕获,后面就没错误了。

注意:

  • catch 写法是针对于整个链式写法的错误捕获的,而 then 第二个参数是针对于上一个返回 Promise 的。
  • 两者的优先级:就是看谁在链式写法的前面,在前面的先捕获到错误,后面就没有错误可以捕获了,链式前面的优先级大,而且两者都不是 break, 可以继续执行后续操作不受影响。

既然有了很多的 Promise,那么我需不需要写很多 catch 呢?

不需要!只需要在末尾 catch 一下就可以了,因为链式写法的错误处理具有“冒泡”特性,链式中任何一个环节出问题,都会被 catch 到,同时在某个环节后面的代码就不会执行了。

setDelay('2000')
.then((result)=>{
  console.log('第一步完成了');
  console.log(result)
  return setDelaySecond(3)
})
.catch((err)=>{ 
  // 这里移到第一个链式去,发现上面的不执行了,下面的继续执行
  console.log(err);
})
.then((result)=>{
  console.log('第二步完成了');
  console.log(result);
})

image.png

链式中的 catch 并不是终点,catch 完如果还有 then 还会继续往下执行。

2.3 Promise 停止跳出