0
点赞
收藏
分享

微信扫一扫

Promise & async/await 相关问题

北溟有渔夫 2022-03-17 阅读 62

系列文章目录

你能实现一个符合 Promises/A+ 规范的Promise吗?Yes, I promise…

文章目录

一、ES7中为什么要提出async/await标准

Promise虽然跳出了异步嵌套的怪圈,用链式表达更加清晰,但是我们也发现如果有大量的异步请求的时候,流程复杂的情况下,会发现充满了屏幕的.then,看起来非常吃力,而ES7Async/Await的出现就是为了解决这种复杂的情况。

二、promise中链式写法需要注意的问题

首先需要明确,then链式写法的本质是一直往下传递一个新的promise对象,也就是说,then在下一步中接受的是上一步return出去的promise

文中用到的两个例子:

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.链式写法的错误处理

then方法接收两个参数(都是回调):

  • 一个是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了。

那么我们把catch挪上去,写到then错误处理前:

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, 可以继续执行后续操作不受影响
  • 实际写的时候,catch的写法会用到更多,,只需要在末尾catch一下就可以了,因为链式写法的错误处理具有“冒泡”特性,链式中任何一个环节出问题,都会被catch到,同时在某个环节后面的代码就不会执行了。
  • 链式中的catch并不是终点!!catch完如果还有then还会继续往下走

2.Promise链式中间想返回自定义的值

Promise的原型方法resolve即可:

setDelay(2).then(res => {
	return Promise.resolve('自定义值')})
.then(res => {
	console.log(res);
})

3.如何跳出或停止Promise链式

不同于一般的functionbreak的方式,如果你是这样的操作:func().then().then().then().catch()的方式,你想在第一个then就跳出链式,后面的不想执行了,不同于一般的break;return null;return false等操作,可以说,如何停止Promise``链,是一大难点,是整个Promise最复杂的地方。
1.用链式的思维想,我们拒绝掉某一链,那么不就是相当于直接跳到了catch模块吗?

setDelay(2000)
.then((result)=>{
  console.log(result)
  console.log('我进行到第一步的');
  return setDelaySecond(1)
})
.then((result)=>{
  console.log('我进行到第二步的');
  console.log(result);
  console.log('我主动跳出循环了');
  return Promise.reject('跳出循环的信息') // 这里返回一个reject,主动跳出循环了
  //当然你也可以直接抛出一个错误跳出:
  //throw new Error('错误信息')
})
.then((result)=>{
  console.log('我不执行');
})
.catch((mes)=>{
  console.dir(mes)
  console.log('我跳出了');
})

2.那有时候我们有这个需求:catch是放在中间(不是末尾),而同时我们又不想执行catch后面的代码,也就是链式的绝对中止,应该怎么办?

setDelay(2000)
.then((result)=>{
  console.log(result)
  console.log('我进行到第一步的');
  return setDelaySecond(1)
})
.then((result)=>{
  console.log('我进行到第二步的');
  console.log(result);
  console.log('我主动跳出循环了');
  return Promise.reject('跳出循环的信息') // 这里直接调用Promise原型方法返回一个reject,主动跳出循环了
})
.then((result)=>{
  console.log('我不执行');
})
.catch((mes)=>{
  console.dir(mes)
  console.log('我跳出了');
})
.then((res)=>{
    console.log('我不想执行,但是却执行了'); // 问题在这,上述的终止方法治标不治本。
})

这时候最后一步then还是执行了,整条链都其实没有本质上的跳出,那应该怎么办呢?
Promise其实是有三种状态的:pending,resolve,rejected,那么我们一直在讨论resolve和rejected这2个状态,是不是忽视了pending这个状态呢?pending状态顾名思义就是请求中的状态,成功请求就是resolve,失败就是reject,其实他就是个中间过渡状态。

而我们上面讨论过了,then的下一层级其实得到的是上一层级返回的Promise对象,也就是说原Promise对象与新对象状态保持一致。那么重点来了,如果你想在这一层级进行终止,是不是直接让它永远都pending下去,那么后续的操作不就没了吗?是不是就达到这个目的了??觉得有疑问的可以参考Promise/A+规范。

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链的问题。

但是!随之而来也有一个问题,那就是可能会导致潜在的内存泄漏,因为我们知道这个一直处于pending状态下的Promise会一直处于被挂起的状态,而我们具体不知道浏览器的机制细节也不清楚,一般的网页没有关系,但大量的复杂的这种pending状态势必会导致内存泄漏

那有没有办法即达到停止后面的链,同时又避免内存泄露呢。有的,具体可以看这里。

三、async/await

1.基于Promise的async/await

什么是async/await呢?可以总结为一句话:async/await是一对好基友,缺一不可,他们的出生是为Promise服务的。

async必须声明的是一个function,不要去声明别的。并且async必须紧跟着function
上面说到必须是个函数(function),那么await就必须是在这个async声明的函数内部使用(必须是直系),否则就会报错。

2.async的本质

async声明的函数的返回本质上是一个Promise
什么意思呢?就是说你只要声明了这个函数是async,那么内部不管你怎么处理,它的返回肯定是个Promise
例子:

(async function () {
    return '我是Promise'//自动解析成return Promise.resolve('我是Promise');
    // 等同于 return new Promise((resolve,reject)=>{ resolve('我是Promise') })
})()
// 返回是Promise
//Promise {<resolved>: "我是Promise"}

3.await的本质与例子

·await·的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖

await顾名思义就是等待一会,只要await声明的Promise异步返回,必须“等待”到有返回值的时候,代码才继续执行下去。

请记住await是在等待一个Promise的异步返回, 普通的异步(例如setTimeout)是没有等待效果的。

4.async/await错误处理

因为 async函数返回的是一个Promise,所以我们可以在外面catch住错误。

5. 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('拒绝'))那么就会进到错误信息里去。

举报

相关推荐

0 条评论