前端 JavaScript 之 Promise _ 前后版

目录

1. 回调函数 callback

2. 回调地狱

3. 认识 Promise

4. Promise 的进阶语法

5. async 函数 和 await 关键字

Promise 简图 :


1. 回调函数 callback

  + 一种封装代码的手段

  + 什么是 callback , 概念

    => 把 函数A 当做 实参 传递到 函数B 内部

    => 在 函数B 内部以 形参 的方式 调用 函数A

    => 我们管这个行为叫做 回调函数

    => 我们说 函数A 是 函数B 的 回调函数

function A() {
  console.log('我是 A 函数')
}

function B(fn) {
  // 此时 fn 形参接受的是书写在 B() 的时候, () 内部的内容 : A
  // 此时 fn 形参接受的就是全局 函数 A 的地址
  // 此时 fn 形参和全局变量 A 操作一个函数空间
  console.log('我是 B 函数')
  // 调用 fn 其实就是在调用执行全局的 A 函数
  fn()
}
// 调用 B 函数
// A 是一个保存 函数的地址
// 把 函数 A 这个地址当做实参传递给了 B 函数内部的 fn 形参
B(A)
// 函数A 是 函数B 的回调函数

  + 为什么需要 callback 回调函数

  + 如果从头到尾都是 同步代码, 不需要回调函数

    => 当你在 封装代码 的时候

    => 并且代码内有 异步 的时候

    => 并且需要在 异步的 末尾 做一些事情的时候

    => 使用 callback

解释: 为什么异步的末尾封装要使用 callback

  + 因为 JS 的单线程

  + 同一个时间点只能做一个事情

  + 主要: 异步的结束时间不确定

  + 例子: 外卖

    => 一个外卖员同一个时间点只能做一件事情

    => 如果你希望多带一双筷子

    => 方案1: 等到外卖员刚好到达店里的时候, 给他打电话

    => 方案2: 在点餐的时候给一个备注


    回调函数的缺点:

  + 回调地狱

  + 当回调 嵌套 回调的时候, 代码的阅读和可维护性不高

  解决回调地狱的问题:

  + Promise 来解决回调地狱

  + 分析:

    => Promise 是来解决回调地狱

    => 回调地狱, 是因为回调函数嵌套过多

    => 回调函数, 为了解决在异步末尾做一些事情的封装

    => Promise 就是一种优雅的对于异步代码封装的方案

// 为什么需要回调函数
// 封装一段代码
// 例子 : 外卖公司做好的事情
function waimai(beizhu) {
  // 获取一个 1000 ~ 6000 的随机整数
  const time = 1000 * Math.round(Math.random() * 5 + 1)
  console.log(' 在路上 ' + time)
  // 我们使用 setTimeout 模拟一个网络环境请求
  setTimeout(() => {
    console.log('到达店里了, 拿到外卖')
    // 直接把我需要执行的代码放在这个位置
    // 那么这个封装就没有意义了
    // 就需要用到回调函数了
    // 因为这个位置是异步的末尾了
    // 这个位置调用 beizhu 就是在异步的末尾调用
    // 例 : 不管什么时候到了店里
    // 拿到外卖以后, 把 备注 的内容执行一下
    beizhu()
  }, time)
}
// 用户的需求: 想多拿一双筷子
waimai(function () { console.log('多拿一双筷子') })
// 用户的需求: 想多拿点辣椒
waimai(function () { console.log('多拿点辣椒') })

 


2. 回调地狱

  + 一种使用回调函数封装的代码时候的情况

  + 回调函数的使用是有 函数嵌套 在里面的

  + 当你大量使用回调函数封装的代码的时候, 会出现 结构紊乱

    => 不利于代码的阅读和维护

  + 为了解决回调地狱

    => ES6 的语法内出现了一个新的语法, 叫做 Promise

    => 为了把 异步代码 封装变成 Promise 语法的封装

    => 不在使用 回调函数 来封装 异步代码了

    => 本质: 用来 封装异步代码 

 实现需求 :

1. 发送一个请求, 请求一个接口

  => 等到响应回来以后

  => 把内容打印在控制台

2. 发送第二个请求, 请求第二个接口

  => 要求必须要在第一个请求结束以后, 打印完毕以后再次发送请求

  => 把响应内容打印在控制台

3. 发送第三个请求, 请求第三个接口

  => 要求, 必须要在第二个请求结束以后, 打印完毕以后再次发送请求

  => 把响应内容打印在控制台

+ 问题1: 在什么位置发送第二个请求 ?

=> ajax 是同步还是异步, 异步的

=> 打开页面, 会马上把第一个请求发送出去

=> 第一个请求还没有回来的时候, 继续发送了第二个请求

=> 因为第一个请求的 success 一定会在请求结束后才执行

=> 我需要把第二个请求的代码放在第一个请求的 success 内部

问题: 代码的阅读和可维护性不高

+ 代码嵌套过多

// 实现需求 1 :
ajax({
  url: 'http://localhost:8888/test/first',
  success: function (res) {
    console.log('第一次请求的结果')
    console.log(res)
    // 实现需求 2 :
	  // 第一个请求结束以后, 才会执行这个位置的代码
	  // 这个时候才会把第二个请求发送出去
    ajax({
      url: 'http://localhost:8888/test/second',
      dataType: 'json',
      success: function (res) {
        console.log('第二次请求的结果')
        console.log(res)
        // 实现需求 3 :
		    // 因为第二个请求结束以后, 才会执行这个位置的代码
		    // 这个时候才会把第三个请求发送出去
        ajax({
          url: 'http://localhost:8888/test/third',
          data: 'name=Jack&age=18',
          dataType: 'json',
          success: function (res) {
            console.log('第三次请求的结果')
            console.log(res)
          }
        })
      }
    })
  }
})

3. 认识 Promise

+ 是一个 ES6 出现的语法

Promise 也是一个 JS 内置的 构造函数

promise - 承诺 :
+ 承诺的状态有多少个 ?
  => 继续(持续执行过程中)
  => 成功
  => 失败
+ 承诺状态之间的转换 : 只能转换一次
  => 要么是 继续 转换成 成功
  => 要么是 继续 转换成 失败
+ Promise 也有三个状态
  => 继续: pending
  => 成功: fulfilled
  => 失败: rejected 

const p = new Promise(function (resolve, reject) {
  // ...
  // ...
})
p.then(function () {
  // ...
})
p.catch(function () {
  // ...
})

Promise 的基础语法 :

  => const p = new Promise(function a( ) {

    // 你要封装的异步代码

  })

  => promise 对象可以调用两个方法

    1. p.then(function ( ) { })

    2. p.catch(function ( ) { })

  + promise对象.then(function ( ) { ... })

    => 给当前这个承诺注册一个 成功以后的函数

  + promise对象.catch(function ( ) { ... })

    => 给当前这个承诺注册一个 失败以后的函数

  如何改变 promise 的状态

  + 在 new Promise 的 a 函数内

  + 可以接受两个参数

    1. 第一个参数: 可以将该 Promise 的状态由继续转换为 成功

    2. 第二个参数: 可以将该 Promise 的状态由继续转换为 失败

// 1. 异步代码
const p = new Promise(function (resolve, reject) {
  // resolve 就是一个转换成功的方法
  // 当你书写 resolve() 的时候, 就是在把 该 promise 的状态转换为成功
  // 就会执行 .then 时候里面书写的 b 函数
  // reject 就是一个转换成失败的方法
  // 当你书写 reject() 的时候, 就是在把 该 promise 的状态转换为失败
  // 就会执行 .catch 时候里面书写的 c 函数
  // 这两个只能书写一个

  // 书写你需要封装的异步代码
  const time = 1000 * Math.round(Math.random() * 5 + 1)
  console.log('承诺一辈子在一起')
  setTimeout(() => {
    // 下面代码一旦执行, 就会把 promise 的状态改成成功
    // resolve()
    // 下面代码一旦执行, 就会把 promise 的状态改成失败
    // reject()
    if (time >= 3000) {
      // 因为会转换为成功 => 两人去世, 埋一个坟
      // 通知一下, 你该烧纸了
      // resolve() 调用的是 then 内部的函数 b
      // 所以这里书写在 () 内部的 time 内容就是给到 then 内 b 的实参
      resolve(time)
    } else {
      // 当做失败, 表示离婚
      // 通知一下, 该去炸坟了
      // reject() 调用的是 catch 内部的函数 c
      // 所以这里书写在 () 内部的 time 内容就是给到 catch 内 c 的实参, 也是报错信息
      reject(time)
    }
  }, time)
})
// promise 对象调用的两个方法
// 注册 成功
p.then(function b(t) {
  // 函数 b 不会被直接调用的
  // 这个位置的代码会在 p 这个 promise 的状态由 继续 转换为 成功 的时候调用执行
  console.log(t, '成功的函数 b')
  // t 就是你在 promise 内部书写的 resolve 的小括号里面 time 的内容
})
// 注册 失败
p.catch(function c(err) {
  // 函数 c 不会被直接调用
  // 这个位置的代码会在 p 这个 promise 的状态由 继续 转换为 失败 的时候调用执行
  console.log(err, '失败的函数 c')
})

 


4. Promise 的进阶语法

+ 当一个 Promise 的 then 内的代码

+ 只要你在前一个 then 内部以 return 返回一个新的 promise 对象 的时候

+ 新 promise 对象的 then 可以直接在前一个 then 的后面继续书写 then

 需求:

  1. 发送一个请求, 请求第一个接口

  2. 发送第二个请求, 请求第二个接口

    => 前提: 必须要等到第一个请求结束以后再次发送

// 初始版 : 
// 需求 1 :
const p = new Promise(function (resolve, reject) {
  // 做异步的事情
  ajax({
    url: 'http://localhost:8888/test/first',
    success: function (res) {
      // 第一个请求成功了
      // res 就是后端给出的结果
      resolve(res)
    }
  })
})
p.then(function (res) {
  console.log('第一次请求结束了')
  console.log(res)
  // 需求2:
  const p2 = new Promise((resolve, reject) => {
    // 做异步的事情
    ajax({
      url: 'http://localhost:8888/test/second',
      dataType: 'json',
      success: function (res) {
        // 第一个请求成功了
        // res 就是后端给出的结果
        resolve(res)
      }
    })
  })
  p2.then(res => {
    console.log('第二个请求结束了')
    console.log(res)
  })
})
// 进阶版 :
const p = new Promise((resolve, reject) => {
  // 做异步的事情
  ajax({
    url: 'http://localhost:8888/test/first',
    success: function (res) {
      resolve(res)
    }
  })
})
// 向拿到 p 的 resolve 的结果
// 就得写 p.then()
p
  .then(function (res) {
    console.log('第一次请求的结果')
    console.log(res)
    // 做第二个事情
    const p2 = new Promise((resolve, reject) => {
      ajax({
        url: 'http://localhost:8888/test/second',
        dataType: 'json',
        success: function (res) {
          resolve(res)
        }
      })
    })

    // 在第一个 then 内部 return 一个 新的 promise 对象 p2
    return p2
  })
  .then(res => {
    console.log('第二次的请求结果')
    console.log(res)
  })

// 使用我按照 promise 形式封装的 pAjax 函数来完成
pAjax({ url: 'http://localhost:8888/test/first' })
  .then(res => {
    console.log('第一个请求结束了')
    console.log(res)

    // return 一个新的 promise 对象
    return pAjax({
      url: 'http://localhost:8888/test/second',
      dataType: 'json'
    })
  })
  .then(res => {
    console.log('第二个请求结果')
    console.log(res)

    // return 一个新的 promise 对象
    return pAjax({
      url: 'http://localhost:8888/test/third',
      data: 'name=Jack&age=20',
      dataType: 'json'
    })
  })
  .then(res => {
    console.log('第三次请求的结果')
    console.log(res)
  })

5. async 函数 和 await 关键字

 + ES7 ~ ES8 之间出现的语法

+ 作用 :

+ 为了解决 Promise 的问题 , 把 Promise 的代码书写的更优雅

+ 核心作用: 把 异步代码 写的 看起来像 同步代码, 本质还是异步

语法:

  => async 关键字 (异步)

  + 使用: 书写在函数的前面

  (可以是声明式函数, 可以是函数表达式, 可以是箭头函数)

    => async function ( ) { }

    => async ( ) => { }

  + 作用:

    1. 该函数内可以使用 await 关键字了

    2. 会把该函数变成一个 异步函数, 只是叫做 异步函数

    (这个异步函数并不是我们真实的异步代码,只是给这个函数起了个名字)

      => 影响的是函数内部的代码 , 不影响函数外面的代码

// async 的语法
async function fn() {}
const fn = async function () {}
const fn = async a => {}

=> await 关键字 (等待)

  + 要求:

    1. await 必须写在一个有 async 关键字的异步函数内部

    2. await 后面等待的内容必须是一个 promise 对象 , 否则等不了

  + 作用:

    => 把 promise 中本该在 then 内代码接受的结果 , 

    可以直接在 await 前面定义变量接受

    => 后续的代码需要等到 promise 执行完毕才会执行

console.log('start')	// ① start
async function fn() {
  console.log('我是 fn 函数内部的代码')	// ②
  // 因为 pAjax 是按照 promise 的语法形式进行封装的代码
  // pAjax 会返回一个 promise 对象
  // fn 函数内, 执行到 pAjax 这个代码的时候
  // 会等待, 等到这个异步的代码完全执行完毕, 把结果赋值给 r1 以后
  // 在继续执行后面的代码
  const r1 = await pAjax({ url: 'http://localhost:8888/test/first' })
  console.log(r1)	// ④
}
fn()
console.log('end')	// ③ end

console.log('start')  // 一 : start
async function fn() {
  // 此时 fn 函数内可以使用 await 关键字了
  // pAjax 返回出来的 promise 对象会执行
  // 把 resolve() 的时候 括号里面的内容 赋值给 r1. 在继续向后执行代码
  const r1 = await pAjax({ url: 'http://localhost:8888/test/first' })
  console.log(r1)   // 三 : 
  // 实现需求2:
  const r2 = await pAjax({
    url: 'http://localhost:8888/test/second',
    dataType: 'json'
  })
  console.log(r2)   // 四 : 
  // 实现需求3:
  const r3 = await pAjax({
    url: 'http://localhost:8888/test/third',
    data: 'name=Jack&age=20',
    dataType: 'json'
  })
  console.log(r3)   // 五 : 
}
fn()
console.log('end')  // 二 : end

Promise 简图 :

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值