目录
简介
在常见的ajax请求中,如果异步请求函数之间存在依赖关系,函数之间就会产生嵌套,过多的嵌套会使得代码看起来十分臃肿并且可读性差、难以维护,这种现象俗称“回调地狱(callback hell)”。ES6标准出现之后,处理异步数据流又多了一种解决方案—构造函数Promise,它比传统的解决方案(回调函数)更加合理和更加强大 。
以下是嵌套多层ajax请求的伪代码:
// ajax请求的伪代码
function ajax(url, sucessCallback, failCallback) {
// url:请求的url
// sucessCallback:成功的回调函数
// failCallback:失败的回调函数
}
ajax(url1, (res1) => {
ajax(url2, (res2) => {
ajax(url3, (res3) => {
doSomething(res1, res2, res3)
})
})
})
构造函数
以下是Promise对象的构造函数:
let promise = new Promise(function(resolve, reject) {
// executor
resolve('123') // 成功并返回'123'
});
在创建promise对象时,我们需要在构造函数中传入一个回调函数称为executor,该函数会立即执行,其参数resolve和reject是js提供的回调函数,用来改变promise的状态(pending、fulfilled、rejected),在执行这两个函数之前其状态为pending,当执行resolve(value)表示任务成功,promise的状态转变为fulfilled并返回结果value,执行reject(error)表示任务失败,promise的状态转变为rejected并返回结果error,这两个结果值在之后的链式调用.then方法中获取。需要注意的是,promise对象的状态一经改变便不会再变。
状态迁移如下图所示:
then方法
当promise对象创建后我们该如何获取它返回的成功或失败的值呢,promise提供了可以进行链式调用的then方法来获取,它提供了两个回调函数,分别用来处理成功和失败的返回值。
用法如下:
// 处理结果
promise.then((res) => {
// 成功结果
console.log(res) // 123
return '456'
}).then((res) => {
// 成功结果
console.log(res) // 456
return Promise.reject('error')
}).then(() => {}, (error) => {
// 失败结果
console.log(error) // error
})
这里需要注意,then方法接收到的值取决于上一个输出,有如下规则:
- 如果then方法中返回的还是一个promise对象则会将该对象继续向下传递到下一个then方法中处理
- 如果then方法返回的是一个普通值,则会将该值传递到下一个then方法的成功回调函数中
- 如果then方法返回一个失败的 Promise,或是抛出一个异常,则会走到下一层的失败的回调函数中去
promise基本用法
// 1. 基本用法
/* resolved 成功时的回调函数 rejected失败时的回调函数 */
let pro = new Promise(function(resolved, rejected) {
let obj = {
result: 200,
data: {
name: 'jack',
age: 23
},
error: '出错了'
}
if(obj.result === 200) {
resolved(obj.data)
} else {
rejected(obj.error)
}
})
/* 接收回调函数传来的值 */
pro.then((result) => {
console.log(result)
}, (err) => {
console.log(err)
})
// pro.catch(err=>{}) 相当于pro.then(null, err=>{})
pro.catch(err => {
console.log(err)
})
Promise.resolve()
创建一个promise对象并且该对象的状态为fulfilled
Promise.resolve()
相当于new Promise(resolve => resolve())
Promise.reject()
创建一个promise对象并且该对象的状态为rejected
Promise.reject()
相当于new Promise((resolve, reject) => reject())
Promise.all()
Promise.all方法提供了并行执行异步操作的功能,接收一个promise对象的数组,当所有异步操作执行完成后才返回结果,并且全部成功才会返回成功结果,如果有一个失败了就会返回失败,返回值也是一个数组,顺序和传入的相同,利用该方法我们可以同时从不同的URL中获取数据。
let p1 = new Promise(resolve => {
setTimeout(resolve, 1000, '123');
})
let p2 = new Promise(resolve => {
setTimeout(resolve, 2000, '456');
})
Promise.all([p1, p2]).then(res => {
console.log(res); // 2s后打印['123', '456']
})
Promise.race()
Promise.race接受一个包含多个promise对象的数组,只要任务完成就返回结果,无论结果是成功还是失败。可以根据方法名的字面意思来理解,看成是数组中的Promise对象进行竞赛,先返回的那个值就是Promise最终的返回值。
// Promise.race() 执行先完成的Promise回调
// 请求图片资源 如果超时则通知用户
function requestImg(url) {
return new Promise(function(resolved, rejected) {
const img = new Image()
img.onload = function() {
resolved(img)
}
img.src = url
})
}
function timeout() {
return new Promise(function(resolved, rejected) {
setTimeout(() => {
rejected(new Error('图片请求超时'))
}, 10000)
})
}
// 接收一个包含promise对象的数组
Promise.race([requestImg('https://img0.baidu.com/it/u=1705694933,4002952892&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1675098000&t=01a5f2c60b89ef81f51c826c9d543b61'), timeout()])
.then(img => {
console.log(img)
document.body.appendChild(img)
}).catch(err => {
console.log(err)
})
Promise.all()与Promise.race()的对比
Promise.race() 和 Promise.all() 一样也是包装多个 Promise 实例,返回一个新的 Promise 实例,只是返回的结果不同。Promise.all() 是所有的任务都处理完才会得到结果,而 Promise.race() 是只要任务成功就返回结果,无论结果是成功还是失败。Promise.all() 在处理多个任务时是非常有用的,比如用 Promise.all() 并发的请求接口,我们希望得到所以接口请求回来的数据之后再去做一些逻辑,这样我们就不需要维护一个数据来记录接口请求有没有完成,而且这样请求的好处是最大限度地利用浏览器的并发请求,节约时间。