其实前一节中,我们在对 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
,现在我们可以了解PromiseState
、PromiseResult
了。
官方图解:
思考:
导致 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)
})
← 【接口风格】 Promise(下) →