Promise对象、同步和异步代码、回调地狱的讲解

0 阅读9分钟

简单粗暴一点的说 Promise 就是一个为了解决异步代码的东西,它可以让代码按照你想要的顺序去执行。

我们先来说说什么是同步代码,什么是异步代码。

  • 同步代码就是按顺序执行,如:12345 按顺序往下走。只有前一条代码执行完毕之后才会去执行后一条代码。
  • 异步代码就是可以不按照顺序执行的代码,如:213465,例如:网络请求、定时器、文件的读写等均是异步的。我们直接上代码直观的去感受一下
setTimeout(() => {
  
}, 1000)

↑这是一个定时器,这是一个可以将包裹在方法体内的代码延迟 1000 毫秒以后再执行的定时器。

console.log('任务1', moment(new Date()).format('HH:mm:ss'))
setTimeout(() => {
  console.log('任务2', moment(new Date()).format('HH:mm:ss'))
}, 1000)

//moment (new Date ()).format ('HH:mm:ss') 如果你是初学者不明白 moment () 是什么,
//直接复制代码发现报错,没关系。并不影响你继续学习,这条代码的功能就是输出博主当前执行代码的时间,
//你可以删掉这条代码,只输出 ' 任务1、任务2' 即可。感兴趣的同学可以自行搜索 moment 的使用方法学习

image.png

可以看到任务 2 比任务 1 的打印时间晚了一秒

前面我们说到定时器是一个异步的代码,我们来验证一下。

setTimeout(() => {
  console.log('任务2', moment(new Date()).format('HH:mm:ss'))
}, 1000)
console.log('任务1', moment(new Date()).format('HH:mm:ss'))

按照代码自上而下的执行顺序,在控制台中应该是一秒钟以后先输出的任务 2 再输出的任务 1

image.png

从控制台的输出得知并不是这样的。代码跳过了任务 2 先输出的任务 1,然后一秒钟之后才输出的任务 2,证实定时器确实是一个异步的代码,它并没有按照顺序执行。

异步代码的好处就在于不会造成代码的堵塞

  • 例如:现在我们是交通参与者的身份开着车行驶在路上,突然前方出现了交通事故,此时边上有辅道可以绕过去,难道我们就要等到这起交通事故处理完成之后才能通行吗?当然是没有这个必要的,我们可以先通过边上的辅道绕过去,无需等到交通事故处理完毕之后再通行,如果等到交通事故处理之后再通行那就会造成交通瘫痪大堵车的结局。

还有的时候异步任务也需要同步的去执行。还是以这个交通事故举例子,例如:我们是交警,我们接到了调度中心发来的任务请求,告知我们某路段发生交通事故需要我们前往现场进行处理。在接警没多久之后调度中心又发来请求,告知我们现在警力紧张,在我们的不远处还有一起交通事故希望我们处理完手里的这个事故之后立马前往下一个路段处理第二起交通事故。那此时任务二就要等待任务一执行完毕之后才能执行。其他的交通参与者当然不需要等待交警的任务执行完毕之后才能通过,可以自行从边上绕过去。上代码,我们把这个小故事以代码的形式呈现出来

console.log('社会车辆1')
console.log('社会车辆2')
setTimeout(() => {
  console.log('交警处理事故1', moment(new Date()).format('HH:mm:ss'))
  setTimeout(() => {
    console.log('交警处理事故2', moment(new Date()).format('HH:mm:ss'))
  }, 1000)
}, 1000)
console.log('社会车辆3')
console.log('社会车辆4')

image.png

通过控制台的输出我们可以看到社会车辆 1、2、3、4 先行通过了,交警处理事故 1 之后一秒钟事故 2 才执行处理。

但其实这种写法并不美观,如果有很多事故需要处理呢,那是不是就要嵌套很多层呢。

console.log('社会车辆1')
console.log('社会车辆2')
setTimeout(() => {
  console.log('交警处理事故1', moment(new Date()).format('HH:mm:ss'))
  setTimeout(() => {
    console.log('交警处理事故2', moment(new Date()).format('HH:mm:ss'))
    setTimeout(() => {
      console.log('交警处理事故3', moment(new Date()).format('HH:mm:ss'))
      setTimeout(() => {
        console.log('交警处理事故4', moment(new Date()).format('HH:mm:ss'))
        setTimeout(() => {
          console.log('交警处理事故5', moment(new Date()).format('HH:mm:ss'))
          setTimeout(() => {
            console.log('交警处理事故6', moment(new Date()).format('HH:mm:ss'))
          }, 1000)
        }, 1000)
      }, 1000)
    }, 1000)
  }, 1000)
}, 1000)
console.log('社会车辆3')
console.log('社会车辆4')

这样的写法会导致代码的可读性非常的差,你可能说我现在一眼就能看出哪儿条输出语句对应着哪儿个代码块。没错,就以现在的这个代码是可以做到。但如果当每一个代码块中的代码复杂起来多起来了以后呢。你还能一眼就看出来吗?当然是很费力的嘛。这就是我们常说的回调地狱

Promise 的出现就可以很好的规避掉这个问题,让我们来进入 Promise 的学习吧。

new Promise()

Promise 就长这样↑

Promise 接收一个函数,在函数中接收两个参数:resolvereject

new Promise((resolve, reject) => {})

resolvereject 是由 Promise 对象传入的。resolve 直译过来的意思是:决定、解决,是在程序执行成功的时候调用的,而 reject 直译过来的意思是:拒绝,是在程序调用失败的时候调用的,故此你可以将

  • resolve 理解为成功
  • reject 理解为失败

例如:现在我们正在登陆某一个网站,当你的用户名或密码输入错误之时,身份验证不通过就会返回错误信息,此时就可以调用 reject。反之用户名和密码都正确身份认证通过了,此时便可调用 resolve

咱们先打印一下这个 Promise 对象,看看长什么样

const promiseObj = new Promise((resolve, reject) => {})
 
console.log(promiseObj)

image.png

我们看到在控制台打印出来的数据中有一个pending(等待的意思),promise 对象中有一个状态的概念。你看现在的状态是一个默认状态 pending,你可以理解为这个 promise 里的 resolvereject 一个都没有被触发。

我们来触发 resolve 参数看看状态

const promiseObj = new Promise((resolve, reject) => {
  resolve()
})
console.log(promiseObj)

image.png

观察发现 PromiseStatepending 等待变成了fulfilledfulfilled 就表示完成或者成功

我们再来触发一下 reject 参数看看状态

const promiseObj = new Promise((resolve, reject) => {
  reject()
})
console.log(promiseObj)

image.png

观察发现 PromiseState 的状态变成了rejected 表示拒绝或者失败了。

promise 对象给我们提供了三个方法,咱们自己可以如图一样通过对象点的形式来看看是不是出来 catchfinallythen 这三个方法。

简单粗暴的讲一下这三个方法。还是以登陆某一网站为例:

  • 假设用户名和密码都填写正确则会走到 then() 方法里,你可以理解为then() 方法是成功时候调用的。假设用户名和密码有一个或者全都填写错误时服务器便会返回错误信息,此时代码就会走到 catch() 方法里,你可以理解为catch()方法是在错误时候调用的。那么finally() 方法则是无论服务器返回的是 true 还是 false 都会走到 finally 里面,你可以理解为不论对错都会调用 finally。用户名密码填写正确,走完 then() 还会走 finally。用户名密码填写错误,走完 catch() 也还会走 finally()

让我们来一一验证一下:

const promiseObj = new Promise((resolve, reject) => {
  resolve('身份认证通过!')
})
promiseObj
  .then((data) => { //data就是resolve传过来的内容 名称没有规定自定义即可
    console.log(data)
  })
  .catch((error) => {
    console.log(error)
  })
  .finally(() => {
    console.log('我是finally')
  })

image.png

通过代码可以看得出这三个方法接收的都是一个回调函数。通过控制台的输出可以得知当我们调用了 resolve 时代码走到了then() 方法之后又走到了 finally() 方法,验证通过。

const promiseObj = new Promise((resolve, reject) => {
  reject('身份认证失败!')
})
promiseObj
  .then((data) => { 
    console.log(data)
  })
  .catch((error) => { //error就是reject传过来的内容  名称没有规定自定义即可
    console.log(error)
  })
  .finally(() => {
    console.log('我是finally')
  })

image.png

通过控制台的输出可以得知当我们调用了 reject 时代码走到了 catch() 方法之后又走到了 finally() 方法,验证通过。

到这儿为止你已经对 Promise 的使用有了一个简单的认识。我们现在用 Promise 来解决一下前面举例说明的小故事。看看你是否会觉得优雅很多,代码更可读。

虽然 promise 本身是同步的,但是 promise.then().catch (). finally () 这些方法中的回调是异步的,所以在这里我们就不用定时器了。

console.log('社会车辆1')
console.log('社会车辆2')
const promiseObj = new Promise((resolve, reject) => {
  resolve('交警处理事故1')
})
promiseObj
  .then((data) => {
    console.log(data)
    return new Promise((resolve, reject) => {
      resolve('交警处理事故2')
    })
  })
  .then((data) => {
    console.log(data)
    return new Promise((resolve, reject) => {
      resolve('交警处理事故3')
    })
  })
  .then((data) => {
    console.log(data)
    return new Promise((resolve, reject) => {
      resolve('交警处理事故4')
    })
  })
  .then((data) => {
    console.log(data)
  })
  .catch()
console.log('社会车辆3')
console.log('社会车辆4')

image.png

有的人会说 这也嵌套了呀。没错这确实是也嵌套了,但是只有一层,无论多少次都只有一层

promiseObj.then().then().then().then().then().then().catch()

↑这样看是不是就更直观了呢。

还有另一种写法可以给每一个.then() 都设置一个单独的 catch() 直接上代码

image.png 从这张图↑可以看到.then() 里面其实可以传递两个参数的,第一个表示成功时候的调用,第二个表示失败时候的调用

promiseObj
  .then((data) => {}, (error) => {})
  .then((data) => {}, (error) => {})
  .then((data) => {}, (error) => {})
  .then((data) => {}, (error) => {})
  .then((data) => {}, (error) => {})

↑这便是大致的一个结构

const promiseObj = new Promise((resolve, reject) => {
  resolve('交警处理事故1')
})
console.log('社会车辆1')
console.log('社会车辆2')
promiseObj
  .then(
    (data) => {
      console.log(data)
      return new Promise((resolve, reject) => {
        resolve('交警处理事故2')
      })
    },
    (error) => {
      console.log(error)
    }
  )
  .then(
    (data) => {
      console.log(data)
      let errorBtn = false
      return new Promise((resolve, reject) => {
        if (errorBtn) {
          resolve('交警处理事故3')
        } else {
          reject('交通事故处理时候遇到特殊状况')
        }
      })
    },
    (error) => {
      console.log(error)
    }
  )
  .then(
    (data) => {
      console.log(data)
      return new Promise((resolve, reject) => {
        resolve('交警处理事故4')
      })
    },
    (error) => {
      console.log(error)
    }
  )
  .then()
 
console.log('社会车辆3')
console.log('社会车辆4')

我们在交警事故处理 3 处加了一个 reject 的调用

image.png

通过控制台的输出可以得知咱们的 reject 起作用了。当交警处理事故 3 处报错时代码就终止了,后面的程序就不执行了。

以上就是本章的知识点讲解分享,感谢大家的耐心观看学习,欢迎大家在评论区讨论纠错,与大家共勉。