其实前一节中,我们在对 ajax 进行封装的时候,success()函数 和 error()函数,就是作为回调函数的存在。

# 一、回调函数方案

  被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数,称为回调函数。

提炼

  简单的说就是:把一个函数 B 作为参数传给要执行的函数 A,并且函数 B 在函数 A 中执行了

  此时就可以称函数 B 为回调函数。

代码理解
  • 同步回调
function greeting(name) {
  alert('Hello ' + name)
}

function processUserInput(callback) {
  var name = prompt('Please enter your name.')
  callback(name)
}

// 执行函数
processUserInput(greeting)
  • 异步回调

JavaScript 一些原生方法中,这种回调非常常见

setTimeout(() => {
  // ……
}, 2000)

// 实现逻辑(函数封装解析)
function setTimeout(callback, time) {
  // 等待多少时间……
  callback()
}

# 1、回调认知

  回调函数可以分为同步回调和异步回调。

// 1、同步回调(nb) -- (遍历相关回调、Promise的✨excutor函数)
const arr = [1, 3, 5]
arr.forEach((item) => {
  console.log(item)
})
console.log('forEach之后打印')

// 2、异步回调(传统) -- (定时器回调、ajax回调、Promise的✨成功|失败的回调)
setTimeout(() => {
  console.log('timout callback')
})

console.log('setTimeout之前打印')

# 2、函数认知

  函数本身就是一个对象

function Fn() {
  // Fn函数
}

const fn = new Fn() // Fn是构造函数  fn是Fn是实例对象(简称🤔Fn对象)

Fn.prototype // Fn是函数对象
Fn.bind({})

# 二、Promise 解决方案

# 1、灵活性

  以传统的 jQuery 请求为例,在没有支持 Promise 之前,使用的是纯回调函数方式。

// 发起$ajax异步任务前必须提前写好成功/失败回调
$.ajax({ ...options, successCallback, failureCallback })

  在 jQuery 支持 Promise 之后:

可以先通过 Promise 启动异步任务,在需要是时候给 Promise 实例对象绑定成功/失败回调即可

const promise = $.ajax({ ...options })

// 随时写好成功/失败回调都可以(例如:2秒后)
setTimeout(() => {
  promise.then(successCallback, failureCallback)
}, 2000)

# 2、链式调用 👀

  多层 callback 回调函数会相互嵌套,从而导致回调地狱问题 —— 嵌套太多会不利于后期维护,或者说不可维护。

setTimeout(() => {
  console.log('延迟 1 秒后输出')

  setTimeout(() => {
    console.log('延迟 2 秒后输出')

    setTimeout(() => {
      console.log('延迟 3 秒后输出')
    }, 3000)
  }, 2000)
}, 1000)

  而 Promise 的链式调用可以很好的解决这个问题:

  • 纯回调函数方式
var name = 'lencamo'

$ajax({
  url: `http://www.liulongbin.top:3006/api/getbooks?author=${name}`,
  success(res) {
    $ajax({
      url: `http://www.liulongbin.top:3006/api/getComment?id=${res[0].id}`,
      success(res2) {
        console.log(res2)
      },
      error(err) {
        console.log(err)
      }
    })
  }
})
  • Promise 链式调用方式
var name = 'lencamo'

$ajax(`http://www.liulongbin.top:3006/api/getbooks?author=${name}`)
  .then((res) => {
    return $ajax(`http://www.liulongbin.top:3006/api/getComment?id=${res[0].id}`)
  })
  .then((res2) => {
    console.log(res2)
  })
  .catch(err) => {
    console.log(err)
  }

  当然,最佳方案是后面的 async / await 方式

答案
async function request() {
  try {
    var name = 'lencamo'

    const res = await $ajax(`http://www.liulongbin.top:3006/api/getbooks?author=${name}`)
    const res2 = await $ajax(`http://www.liulongbin.top:3006/api/getComment?id=${res[0].id}`)

    console.log(res2)
  } catch (err) {
    console.log(err)
  }
}

# 三、Promise 实例对象

  Promise 是 JavaScript 中处理异步操作的一种解决方案,相比回调函数方案更加合理、更加强大。ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。

应用:promise 封装一个 ajax
function ajax(url) {
  return new Promise((resolve, reject) => {
    // 1、创建xhr对象
    var xhr = new XMLHttpRequest()

    // 2、发生get请求
    xhr.open('GET', url, true) // 采用异步✨,所以要加上true
    xhr.send()

    // 监听事件
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        // 3、返回响应数据
        if (xhr.status >= 200 && xhr.status <= 300) {
          // 成功
          resolve(JSON.parse(xhr.responseText))
        } else {
          // 失败
          reject(xhr.responseText)
        }
      }
    }
  })
}

ajax('http://www.liulongbin.top:3006/api/getbooks')
  .then((res) => {
    console.log(res)
  })
  .catch((error) => {
    console.log(error)
  })

  通过使用 Promise,可以更好地管理异步操作,并在操作完成时处理结果或错误。

# 1、promise 实例 ✨

  Promise 对象表示一个尚未完成但预期将在未来完成的操作,其结果可以是成功或失败。

创建 Promise 对象
const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    const result = Math.random()
    if (result >= 0.5) {
      // 完成操作
      resolve(result)
    } else {
      // 操作失败
      reject('操作失败')
    }
  }, 1000)
})

创建一个 Promise 对象时,必须传入一个执行器(executor)函数(它会在 Promise 对象被创建时立即执行)作为参数。

// 必须接受参数✨(excutor执行器)
var promObj = new Promise((resolve, reject) => {})

console.log(promObj)

打印结果:

观察 Promise 上的原型方法:

  -.then函数可传两个参数,.catch函数可传一个参数。

var promObj = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve(); // 成功
    reject() // 失败
  })
})

promObj
  .then(
    () => {
      console.log('请求成功')
    },
    () => {
      console.log('请求失败1')
    }
  )
  .catch(() => {
    console.log('请求失败2')
  })

# 2、失败回调

  then 方法中的第二个参数(简称:失败回调 1)和 catch 方法中的回调函数(简称:失败回调 2)都可以处理 Promise 对象的拒绝情况。但是两者之间也有些许区别:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('操作失败')
  }, 1000)
})

# 失败回调 1

.then 中的失败回调函数 优先级要高于 .catch 中的失败回调函数

promise
  .then(
    (res) => {
      console.log('操作成功:', res)
    },
    (err) => {
      console.log('失败回调1', err)
    }
  )
  .then(() => {
    console.log('后续then方法被执行')
  })
  .catch((err) => {
    console.log('失败回调2:', err)
  })

结果:

失败回调1: 操作失败
后续then方法被执行

# 失败回调 2

promise2
  .then((res) => {
    console.log('操作成功:', res)
  })
  .then(() => {
    console.log('后续then方法被执行')
  })
  .catch((err) => {
    console.log('失败回调2:', err)
  })

结果:

失败回调2: 操作失败

# 3、promise 状态 ✍

  Promise 对象通过自身的状态,来控制异步操作。其中,Promise 实例具有三种状态:

待定(pending) 初始状态,既没有被兑现,也没有被拒绝。
已兑现(fulfilled) 意味着操作成功完成。
已拒绝(rejected) 意味着操作失败。

  前面我们关注的是原型对象 Prototype,现在我们可以了解PromiseStatePromiseResult了。

官方图解:

思考:

导致 Promise 状态变化有哪些方式?

提示

  执行器函数通常接收的两个参数 resolve 和 reject 是由 Promise 对象提供的函数,用于将 Promise 对象的状态从等待(pending)转换为已完成(fulfilled)或已拒绝(rejected)。

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve('操作成功') // promise 变为 resolved状态
    // reject('操作失败') // promise 变为 rejected状态

    throw new Error('出现异常') // promise 变为 rejected状态
  }, 1000)
})

console.log(promise)

# 4、finally 方法

  通过上面的学习,我们发现了:可以在.then 中执行成功的回调,在.catch 和 .catch 中执行失败的回调。

  那有没有,不管是成功还是失败,都可以执行的回调呢?

console.log('显示loading……')

ajax('http://www.liulongbin.top:3006/api/getbooks')
  .then((res) => {
    console.log('success', res)
  })
  .catch((err) => {
    console.log('error', err)
  })
  .finally(() => {
    console.log('隐藏loading……')
  })

# 四、Promise 函数对象

  前面,我们对 Promise 的使用中,我们都是先创建一个 promise 构造函数,然后在 promise 对象实例上使用 Promise 的原型方法(.then、.catch)

  但是 Promise 不仅是一个构造函数,也是一个对象;我们可以直接调用 Promise 函数对象身上的 resolve、reject 等方法,这些方法会返回一个新的 Promise 实例。

# 1、Promise.resolve

作用:

Promise.resolve()方法可以将几乎所有类型的数据转换成 Promise 对象

// 将数据转换为Promise对象 👏
// 1、简写
let promise = Promise.resolve({ name: 'lencamo' })

// 2、完整
// let promise = new Promise((resolve) => resolve({ name: 'lencamo' }))
应用:数据缓存

数据缓存(对入门部分的 ajax 封装进行升级)

function ajax(url) {
  // 1、缓存判断
  var cache = ajax.cache || (ajax.cache = { data: null })
  if (cache.data) {
    console.log('走缓存')
    // return cache.data  ✖ 走缓存时,返回的应该是一个Promise对象
    return Promise.resolve(cache.data) // ✨
  }

  return new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url, true)
    xhr.send()
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status <= 300) {
          // 成功
          resolve(JSON.parse(xhr.responseText))

          // 2、缓存数据赋值
          console.log('不走缓存')
          ajax.cache.data = JSON.parse(xhr.responseText)
        } else {
          // 失败
          reject(xhr.responseText)
        }
      }
    }
  })
}

ajax('http://www.liulongbin.top:3006/api/getbooks')
  .then((res) => {
    console.log(res)
  })
  .catch((error) => {
    console.log(error)
  })

setTimeout(() => {
  ajax('http://www.liulongbin.top:3006/api/getbooks')
    .then((res) => {
      console.log(res)
    })
    .catch((error) => {
      console.log(error)
    })
}, 2000)

# 2、Promise.reject

作用:

Promise.reject()方法和上面的 Promise.resolve()一样,不同的是这里返回的是成功的回调。

// 1、简写
const p = Promise.reject('error')

// 2、完整
// const p = new Promise((resolve, reject) => reject('error'))

// console.log(p)
p.catch((err) => {
  console.log(err)
})
应用:非常规接口

非常规接口(响应 200,但返回数据是 null)

var p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(null)
  })
}, 1000)

p.then((res) => {
  console.log(res)

  if (res) {
    console.log('渲染页面函数调用')
  } else {
    // 方式1
    // throw new Error('error')
    // 方式2 ✨
    return Promise.reject('error') // 这样数据就catch中打印
  }
}).catch((err) => {
  console.log(err)
})
更新于 : 8/7/2024, 2:16:31 PM