最易理解的Promise对象教程
持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
Promise是异步编程的一种解决方案,它最早由社区提出并实现,ES6将Promise写进了语言标准,并原生提供Promoise对象。
一项技术的产生,必定是解决特定问题。那么,Promise解决了什么问题呢?
一 为什么需要Promise?
1.1 什么是回调地狱
常见的一个需求:通过第一次Ajax请求获取到id,然后再发起第二次Ajax请求,根据id获取用户名username,最后发起第三次Ajax请求,根据用户名username获取所在单位company。
我们模拟一下响应数据,通过jquery的ajax方法来实现。
data.json
{
"id":1
}
data2.json
{
"username":"沈麽鬼"
}
data3.json
{
"company":"红鲤鱼与绿鲤鱼与驴公司"
}
示例 思考:ajax在哪里发送第二个请求?
// 发送ajax请求
$.ajax({
type:'GET',
url:'./data.json',
success: (res) => { //response,result
console.log(res);
const id = res.id //拿到响应的结果
console.log(id); // =>id = 1
}
})
// 思考:ajax在哪里发送第二个请求??
$.ajax({
type:'GET',
url:'./data2.json',
data:{
id
},// 这里能获取到id吗?id is not defined 不能!!id的作用域不同
success: (res) => {
console.log(res);
}
})
在上面的例子中,第二次的请求参数id依赖于前一次的响应结果,由于id的作用域不同,第二次ajax请求拿不到id,所以不能成功!
那么第二次ajax请求,要嵌套第一个ajax里面的success回调函数中。
示例
// 发送ajax请求
$.ajax({
type:'GET',
url:'./data.json',
success: (res) => { //response,result
const id = res.id //拿到响应的结果
// 第二次ajax请求
$.ajax({
type:'GET',
url:'./data2.json',
data:{
id
},
success: (res) => {
console.log(res); // =>{username: "拓宇"}
}
})
}
})
同样,第三次ajax请求,要嵌套第二个ajax里面的success回调函数中。
示例
// 发送ajax请求
$.ajax({
type:'GET',
url:'./data.json',
success: (res) => { //response,result
const id = res.id //拿到响应的结果
// 第二次ajax请求
$.ajax({
type:'GET',
url:'./data2.json',
data:{
id
},
success: (res) => {
const userName = res.username
// 第三次ajax请求
$.ajax({
type:'GET',
url:'./data3.json',
data:{
userName
},
success: (res) => {
console.log(res); // =>{company: "红鲤鱼与绿鲤鱼与驴公司"}
}
})
}
})
}
})
上面的案例中,在回调函数中嵌套回调函数,这种形式,我们就叫做回调地狱。
回调地狱的问题一是代码结构很乱,调试不方便,二是业务需求变化时,不便维护。
1.2 Promise的最佳实践
为了解决这个问题,社区提出并实现Promise对象,它是用来解决回调地狱,以线性then()方法调用链的形式表达一连串异步操作,而无须把每个操作嵌套在前一个操作的回调内部,使我们代码看起来更加易读、优雅、扁平化。
二 Promise的基本使用
Promise是一个首字母大写的函数,一般来说,首字母大写的函数,是构造函数。构造函数的作用是通过new关键字实例化对象。
示例 基础语法
new Promise((resolve,reject)=>{})
-
23 Promise接受一个函数作为参数
-
在这个函数中,接受两个参数:
- resolve 成功函数
- reject 失败函数
Promise实例有两个属性,PromiseStatus和PromiseValue属性
- PromiseStatus:状态
- PromiseValue:结果
// 调用resolve()
const p = new Promise((resolve, reject) => {
})
console.dir(p); // PromiseStatus和PromiseValue属性
2.1 Promise的状态
第一种状态:pending (准备,待解决,进行中)
第二种状态:resolved(已完成,成功)
第三种状态:rejected(已拒绝,失败)
2.2 Promise状态的改变
通过调用resolve()和reject()改变当前promise对象 的状态。
示例
// 调用resolve()
const p = new Promise((resolve, reject) => {
// resolve() 调用函数,可以使当前Promise对象状态改成 resolved
resolve() // => PromiseStatus:resolved
})
console.dir(p);
// 调用reject()
const p = new Promise((resolve, reject) => {
// resolve() 调用函数,可以使当前Promise对象状态改成 resolved
// resolve() // => PromiseStatus:resolved
// reject() 调用函数,可以使当前Promise对象状态改成 rejected
reject()
})
console.dir(p); // => PromiseStatus:rejected
- 调用 resolve() 函数,可以使当前Promise对象状态改成 resolved
- 调用reject()函数,可以使当前Promise对象状态改成 rejected
Promise的状态是一次性的,即如果调用一次resolve(),再调用reject(),那么reject()不生效。
示例
const p = new Promise((resolve, reject) => {
resolve()
reject() // 不生效
})
console.dir(p); // => PromiseStatus:resolved 而不是 rejected
2.3 Promise的结果
示例
// 通过调用resolve函数,传递参数,改变当前promise对象的结果
const p = new Promise((resolve, reject) => {
resolve('成功')
})
console.dir(p); // => PromiseValue:"成功"
// 通过调用reject函数,传递参数,改变当前promise对象的结果
const p = new Promise((resolve, reject) => {
reject('失败')
})
console.dir(p); // => PromiseValue:"失败"
promise的结果,是使用resolve或reject的传递参数,来改变PromiseValue。
三 Promise的方法
3.1 then()方法的使用
then()方法接受两个参数,两个参数都是函数。
第一个函数当promise状态为resolved时,resolve('成功')执行;
第二个函数当promise状态为rejectded时,reject('失败')执行;
示例
const p = new Promise((resolve, reject) => {
resolve('成功')
// reject('失败')
})
// then方法,方法即是就是函数。
// 函数参数
// 第一个参数是一个函数 resolve() 执行时调用
// 第二个还是一个函数 reject() 执行时调用
// 返回值
p.then(() => {
// 当promise的状态是resolved时 调用
console.log("成功时调用");
},() => {
// 当promise的状态是rejected时 调用
console.log("失败时调用");
})
console.dir(p);
3.2 then()方法获取数据
通过then方法第一个函数参数中的形参value,拿到resolve('成功')传递的参数;
通过then方法第二个函数参数中的形参error,拿到reject('失败')传递的参数;
示例
const p = new Promise((resolve, reject) => {
resolve('成功')
// reject('失败')
})
// then方法,方法即是就是函数。
// 函数参数
// 第一个参数是一个函数 resolve() 执行时调用
// 第二个还是一个函数 reject() 执行时调用
// 返回值
p.then((value) => {
// value拿到的是 resolve()传递的参数
console.log(value); // => "成功"
},(error) => {
// error拿到的是 reject()传递的参数
console.log(error);
})
3.3 then()方法的返回值
我们通过打印p.then(),可以看到,then()方法的返回值是一个新的promise对象。
示例
const p = new Promise((resolve, reject) => {})
// 返回值 是一个新的promise示例,状态是pending
p.then(() => {}, () => {})
console.dir(p.then()); // 返回的是一个新的promise对象
3.4 then ()链式操作
那么,then()既然是一个promse对象,那它也应该有then(),于是我们可以在promise实例中,使用then方法的链式操作。
示例
// 链式操作
const p = new Promise((res,rej) => {
}).then().then().then()
3.5 catch()方法
catch()捕获错误,它在两种情况下被执行:
- 当promise的状态改成rejected时,执行
- 当promise代码块中,出现代码错误时,执行
示例
const p = new Promise((resolve, reject) => {
// reject()
console.log(a);
})
// 思考: catch() 什么时候执行?
// 1 当promise的状态改成rejected时,执行
// 2 当promise代码块中,出现代码错误时,执行
p.catch((error) => {
console.log(error); // ReferenceError: a is not defined
})
可以发现,catch()和then ()第二个参数函数所触发时机一样。
我们在实际开发中,常常会使用catch()来捕获错误,很少用到then()方法的第二个函数,那么 Promise最常用的用法是下面这个结构:
// Promise 最佳实践代码
new Promise((res, rej) => {
}).then((value) => {
console.log(value);
}).catch((error) => {
console.log(error);
})
四 解决回调地狱
4.1 promise 实现
我们回到ajax回调地狱的需求,通过三次嵌套回调,依次从data.json、data2.json、data3.json拿到最终数据。
示例
// 解决回调地狱
new Promise((resolve, reject) => {
$.ajax({
type:'GET',
url:'./data.json',
success: (res) => {
// 调用res 修改promise状态为成功
resolve(res);
},
error:(error) => {
reject(error)
}
})
}).then((data) => {
const id = data.id
return new Promise((resolve, reject) => {
$.ajax({
type:'GET',
url:'./data2.json',
data:id,
success: (res) => {
// 调用res 修改promise状态为成功
resolve(res);
},
error:(error) => {
reject(error)
}
})
})
}).then((data) => {
const userName = data.username
return new Promise((resolve, reject) => {
$.ajax({
type:'GET',
url:'./data3.json',
data:userName,
success: (res) => {
// 获取data3中的数据
console.log(res);
},
error:(error) => {
console.log(error);
}
})
})
})
上述代码,使得异步回调函数不再嵌套,可读性、可维护性要比回调嵌套的方式优雅很多。
4.2 优化代码-封装Ajax
在4.1的例子中会发现,ajax的代码几乎是通用的,只不过url和参数不同,重复的代码产生了一些冗余,接下来我们把ajax的请求封装成一个函数。
通过then()链式操作,使得异步方法写起来向同步方法一样,代码看起来更加整洁、清晰。
示例
/**
* 封装Ajax请求
*/
function getData(url, data = {}) {
return new Promise((resolve, reject) => {
$.ajax({
type: 'GET',
url: url,
data: data,
success: (res) => {
resolve(res);
},
error: (error) => {
reject(error)
}
})
})
}
getData('./data.json')
.then((data) => {
const id = data.id
return getData('./data2.json', id)
})
.then((data) => {
const userName = data.username
return getData('./data3.json', userName)
}).then((data) => {
console.log(data.company); // => "红鲤鱼与绿鲤鱼与驴公司"
})
Promise对象还有其他的方法和一些特性,本文不再概述,大家可以查看Promise文档 ,这里推荐阮一峰老师的《ES6入门教程》进行学习。