简介
Promise 是 ES6 引入的一个新的概念,它是一种对异步操作进行封装的方式。它可以使异步操作更加灵活、更加清晰、更加易于维护。
- 有三个状态:
Pending(等待状态)、Fulfilled(成功状态)、Rejected(失败状态)- 在 Promise 对象的生命周期中,可以从 Pending 状态转换到 Fulfilled 或 Rejected 状态。
- 它支持链式调用,可以解决传统回调函数存在的 “回调地狱” 问题。
- 有两个关键的方法:
then()和catch()。promise.then()方法用于指定在 Promise 对象状态从 pending 变为 fulfilled 时的回调函数,promise.catch()方法用于指定在 Promise 对象状态从 pending 变为 rejected 时的回调函数。
Promise 可以封装任何异步操作,包括但不限于:基于回调函数的异步编程、事件监听与处理、兼容其他异步编程库等。当需要进行异步编程时,可以考虑使用 Promise 封装异步操作,从而避免回调函数嵌套带来的困扰。
回调地狱
回调地狱是指在异步编程中,嵌套多层回调函数导致代码可读性变差、难以维护,进而带来困扰的现象。在回调地狱中,一个异步操作导致了另外一个异步操作,而后者的回调函数又导致了另外一个异步操作,以此类推。这种操作层层嵌套的方式导致代码变得难以理解、难以维护。
例如:
假设我们需要进行异步操作来从服务器上获取用户信息,然后根据用户信息再发起两个异步操作,依次从服务器上获取用户订单和用户地址信息
getUserInfo((userInfo) => {
getOrderInfo(userInfo.userId, (orderInfo) => {
getAddressInfo(userInfo.userId, (addressInfo) => {
console.log("用户信息:" + userInfo);
console.log("订单信息:" + orderInfo);
console.log("地址信息:" + addressInfo);
});
});
});
在这个例子中,我们需要先获取用户信息,然后根据用户信息获取订单和地址信息。由于异步操作需要等待服务器的响应,因此我们需要使用回调函数来处理异步操作的结果。然而由于需要进行多个异步操作,因此这些回调函数被嵌套在一起,导致了回调地狱的出现,一旦嵌套的层数变多,代码将变得非常臃肿和难以理解。
- 这里仅仅是举例三个回调,如果我们想要操作多个异步函数,就容易看不懂,难理解。除此之外还会存在两个问题:
- 命名不规范。
- 不能很好地捕获错误,很难进行错误处理
Promise 解决回调问题
- 如何解决回调地狱问题 ?
- 规范回调的名字或顺序
- 拒绝回调地狱,让代码可读性更强
- 很方便地捕获错误
我们利用 Promise 可以完美解决以上三个问题。例如对上面的例子我们可以用 Promise 改写:
getUserInfo()
.then((userInfo) => {
return getOrderInfo(userInfo.userId);
})
.then((orderInfo) => {
return getAddressInfo(userInfo.userId);
})
.then((addressInfo) => {
console.log("用户信息:" + userInfo);
console.log("订单信息:" + orderInfo);
console.log("地址信息:" + addressInfo);
})
.catch((err) => {
console.log(err);
});
在这个例子中,我们首先调用 getUserInfo 方法来获得用户信息。在 then 方法中,我们返回一个 Promise,该 Promise 执行 getOrderInfo 方法。当 getOrderInfo 方法完成后,它将返回一个 Promise,该 Promise 在下一个 then 方法中执行 getAddressInfo 方法。最后,当 getAddressInfo 方法完成时,最后一个 then 会执行。如果任何一个异步操作失败,就会跳转到 catch 方法中进行错误处理。
以 AJAX 的封装为例,来解释一下 Promise 的用法
- 封装一个AJAX
ajax = (method, url, options)=>{
const {success, fail} = options //析构赋值,es6新语法。表示从options中获取success和fail。等于:const success = options.success ; const fail = options.fail
const request = new XMLHttpRequest()
request.open(method, url)
request.onreadystatechange = ()=>{
if(request.readyState === 4){ //成功就调用 success,失败就调用 fail
if(request.status < 400){
success.call(null, request.response) //this为null
}else if(request.status >= 400){
fail.call(null, request, request.status)
}
}
}
request.send()
}
- 封装这样一个ajax后,之后可以直接使用下面的方法:
ajax('get', '/xxx', {
success(response){}, fail: (request, status)=>{}
}) // 左边是 function 缩写,右边是箭头函数
- 改用 Promise 的方法:
ajax('get', '/xxx')
.then((response)=>{}, (request)=>{})
虽然也是回调。但是不需要记 success 和 fail 了
- then 的第一个参数就是 success
- then 的第二个参数就是 fail
- promise规范 success 在前,fail 在后
在这里 ajax() 返回了一个含有 .then() 方法的对象。如何得到这个含有 .then() 的对象呢?那就要改造 ajax 的源码了,如下:
ajax = (method, url, options)=>{
return new Promise((resolve, reject)=>{
const {success, fail} = options
const request = new XMLHttpRequest()
request.open(method, url)
request.onreadystatechange = ()=>{
if(request.readyState === 4){ // 成功就调用 resolve,失败就调用 reject
if(request.status < 400){
resolve.call(null, request.response)
}else if(request.status >= 400){
reject.call(null, request)
}
}
}
request.send()
})
}
直接返回一个构造函数 Promise ,Promise 接受一个箭头函数,包裹了ajax 函数体。箭头函数必须声明两个参数(resolve, reject),注意和 then 的两个参数不是同一个。它们的.call()只接受一个参数,并且this是空。
总结
如何将一个回调的异步函数变成promise异步函数?
- 第一步
return new Promise((resolve,reject)=>{...})- 任务成功则调用
resolve(result) - 任务失败则调用
reject(error) - resolve 和 reject 会再去调用成功和失败函数
- 第二步
- 使用
.then(success, fail)传入成功和失败函数
- 使用
资料来源:饥人谷