阅读视图

发现新文章,点击刷新页面。

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

简单粗暴一点的说 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 处报错时代码就终止了,后面的程序就不执行了。

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

v-bind 你用对了吗?

先极简总结(2 句话记死,终身受用)

  1. 绑 1 个属性:用缩写 :属性名="值"(原生标签 / Vue 组件通用,日常 90% 用这个);
  2. 绑 N 个属性:用 v-bind="属性对象"(无冒号、无属性名,属性越多越简洁,封装组件 / 配置驱动场景必用)。

核心前提先讲透(一句话干货)

  • v-bind是 Vue 用来给HTML 标签 / Vue 组件绑定动态属性值的指令,只有 2 种核心用法,本质都是「动态传值」,区别仅在于绑 1 个属性还是绑多个属性
  1. 单个属性绑定v-bind:属性名="值" → 缩写 :属性名="值"(日常 90% 高频用);

  2. 批量属性绑定:直接v-bind="属性对象"(无属性名、无冒号,把多个属性打包成对象一次性绑定);

关键:批量绑定的属性对象键 = HTML 标签 / Vue 组件的属性名值 = 要绑定的动态数据,Vue 会自动解析成「键 = 值」的单个属性,一一绑定到标签上。

例子 :原生img图片标签(最易理解的通用场景)

img标签的src(图片地址)、alt(占位文字)、width(宽度)是所有人都认识的原生属性,用它举例最直观,重点看单个绑定批量绑定的等价关系。

步骤 :先定义 Vue 里的动态数据(脚本部分,通用)

<script setup> 
    // Vue3基础响应式数据,不用纠结ref,知道是动态值就行 
    import { ref } from 'vue' 
    // 图片地址 
    const imgUrl = ref('https://picsum.photos/200/200')
    // 图片占位文字 
    const imgText = ref('风景图') 
    // 图片宽度 
    const imgW = ref(200)
</script>

用法 1:单个属性绑定(缩写:,逐个绑)

最常用的写法,给img逐个绑定动态属性,每个属性都用:缩写,模板清晰:

<template> 
    <!-- 核心:每个原生属性前加:,绑定对应的动态值 --> 
    <img :src="imgUrl" :alt="imgText" :width="imgW" /> 
</template>

等价于完整v-bind写法(繁琐,几乎没人用):

<script setup> 
    import { ref, reactive } from 'vue' 
    // 1. 先定义零散动态值 
    const imgUrl = ref('https://picsum.photos/200/200') 
    const imgText = ref('风景图') 
    const imgW = ref(200) 
    // 2. 打包成「属性对象」:键=img原生属性名,值=动态值 
    const imgProps = reactive({ src: imgUrl, alt: imgText, width: imgW }) 
</script> 
<template> 
    <!-- 核心:无属性名、无冒号,v-bind直接绑属性对象 --> 
    <img v-bind="imgProps" /> 
</template>

用法 2:批量属性绑定(v-bind="对象",打包绑)

img需要的所有动态属性打包成一个对象,用v-bind直接绑定这个对象,Vue 会自动解析:

<script setup> 
    import { ref, reactive } from 'vue' 
    // 1. 先定义零散动态值 
    const imgUrl = ref('https://picsum.photos/200/200') 
    const imgText = ref('风景图') 
    const imgW = ref(200) 
    // 2. 打包成「属性对象」:键=img原生属性名,值=动态值 
    const imgProps = reactive({ src: imgUrl, alt: imgText, width: imgW }) 
</script> 
<template> 
    <!-- 核心:无属性名、无冒号,v-bind直接绑属性对象 --> 
    <img v-bind="imgProps" /> 
</template>

完全等价于单个绑定的写法,Vue 会自动把imgProps里的src/alt/width逐个绑定到img标签上,效果一模一样。

单个和批量这两种用法是 Vue 最基础、最高频的语法,不用记复杂概念,只要知道「单个用:,多个用 v-bind = 对象」,就能搞定所有动态属性绑定场景。

❌