Promise
是 JavaScript 中用于处理异步操作的对象。它代表了一个承诺,表示一个异步操作的最终完成结果(成功或失败),并可以使用链式的方式处理这个结果。
Promise
对象可以有以下三个状态:
注意:每个 Promise 对象一旦被兑现/拒绝,那就是已敲定了,状态无法再被改变
一个 Promise
对象在状态发生改变时,会触发相应的状态处理函数。这些处理函数包括:
then(onFulfilled, onRejected)
: 注册一个处理成功完成的回调函数(onFulfilled
)和一个处理失败的回调函数(onRejected
)。catch(onRejected)
: 注册一个处理失败的回调函数。finally(onFinally)
: 注册一个无论成功还是失败都会执行的回调函数。Promise 管理异步任务,语法怎么用?
// 1. 创建 Promise 对象
const p = new Promise((resolve, reject) => {
// 2. 执行异步任务-并传递结果
// 成功调用: resolve(值) 触发 then() 执行
// 失败调用: reject(值) 触发 catch() 执行
})
// 3. 接收结果
p.then(result => {
// 成功
}).catch(error => {
// 失败
})
以下是一个 Promise
对象的示例:
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const randomNumber = Math.random();
if (randomNumber < 0.5) {
resolve(randomNumber);
} else {
reject(new Error('操作失败'));
}
}, 1000);
});
promise.then((result) => {
console.log('成功:', result);
}).catch((error) => {
console.error('失败:', error);
}).finally(() => {
console.log('无论成功或失败都会执行');
});
在这个示例中,Promise
构造函数接收一个带有两个参数的回调函数,这两个参数分别是 resolve
和 reject
。当异步操作成功完成时,调用 resolve
并传递结果;当异步操作失败时,调用 reject
并传递一个错误对象。
通过调用 then
方法,我们可以注册一个处理成功完成的回调函数,catch
方法可以注册一个处理失败的回调函数,而 finally
方法可以注册一个无论成功还是失败都会执行的回调函数。
使用 Promise
的好处是它可以处理异步操作的结果,而不是用回调函数嵌套回调函数,使代码更清晰、更易读。此外,Promise
还支持链式调用,允许在不同的异步操作之间进行流畅的操作,避免了回调地狱的问题。
除了手动创建 Promise
对象,许多现代的 JavaScript 异步操作也会返回一个 Promise
对象,如浏览器的 Fetch API、使用 fetch
进行网络请求、使用 axios
进行 Ajax 请求等都是基于 Promise
的。这让异步程序更加易于编写和组织。
让我们更深入地了解 Promise
和它的工作机制。
Promise
构造函数
Promise
是一个构造函数,用于生成一个 Promise
实例。构造函数接收一个执行器函数作为参数,这个执行器函数又接收两个函数作为参数:resolve
和 reject
。
const promise = new Promise((resolve, reject) => {
// 异步操作代码
});
resolve
函数,并传入值作为 Promise
的结果。reject
函数,并传入一个 Error
对象或其他错误信息作为 Promise
的失败原因。Promise
状态转换
一个 Promise
的状态一旦从 pending
改变为 fulfilled
或 rejected
,它就不能再次改变状态。resolve
函数会将 Promise
的状态从 pending
设置为 fulfilled
,而 reject
会将状态设置为 rejected
。
Promise.prototype.then()
then
方法接收两个可选参数,分别对应状态变为 fulfilled
和 rejected
时的回调函数。
// 成功和失败都注册回调函数
promise.then(
value => { /* 处理成功 */ },
error => { /* 处理失败 */ }
);
then
方法返回一个新的 Promise
实例,并不是原来的 Promise
,这允许它进行链式调用。链式调用中,每一次 then
的返回都可以被下一个 then
接收。
Promise.prototype.catch()
catch
方法是 then(null, onRejected)
的语法糖,用于注册当 Promise
状态变为 rejected
时的回调函数。
promise.catch(
error => { /* 处理失败 */ }
);
同样地,catch
返回一个新的 Promise
实例,可以接着进行链式调用。
Promise.prototype.finally()
finally
方法注册一个回调函数,无论 Promise
的状态如何,这个回调函数都会被执行。
promise.finally(() => {
// 不论成功或失败,都会执行的代码
});
finally
通常用于清理操作,如关闭文件流或数据库连接等。
Promise
静态方法
fulfilled
的 Promise
。const promise = Promise.resolve(42);
rejected
的 Promise
。const promise = Promise.reject(new Error('出错了'));
Promise
数组作为输入,并返回一个新的 Promise
实例。这个新的 Promise
只有在所有输入的 Promise
都成功完成时才会成功(状态为 fulfilled
)。如果有任意一个输入的 Promise
失败,新的 Promise
会立即失败(状态为 rejected
)。Promise.all([promise1, promise2]).then(results => {
// 所有 promise 成功解决时的处理
}).catch(error => {
// 任意一个 promise 失败时的处理
});
Promise
数组作为输入。但它返回的新 Promise
实例将采用数组中第一个改变状态的 Promise
的结果作为它的结果。Promise.race([promise1, promise2]).then(result => {
// 第一个解决的 promise 的处理
}).catch(error => {
// 第一个失败的 promise 的处理
});
Promise.allSettled: 它返回一个在所有给定的 Promise
已被解决或被拒绝后的 Promise
,并带有一个对象数组,每个对象表示对应的 Promise
的结果。
Promise.any: 可以等待多个 Promise
的其中一个成功完成,返回第一个成功完成的 Promise
的结果。如果所有 Promise
都失败了,返回一个聚合错误。
错误处理
当 Promise
执行器中抛出一个异常时,这个异常会被捕获,并使得 Promise
失败,即状态变为 rejected
。这个异常会传递给 catch
方法或 then
方法中注册的失败回调函数。
new Promise((resolve, reject) => {
throw new Error('出错了');
}).catch(error => {
console.error(error); // '出错了'
});
链式调用
Promise
支持链式调用,允许我们进行更复杂的异步任务序列化处理。每个 then
或 catch
返回一个新的 Promise
,可以通过 then
或 catch
链接下一个处理过程。
fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log(data);
return data; // 返回的数据可以再次被下一个 .then 使用
})
.catch(error => {
console.error('Error fetching data:', error);
})
.finally(() => {
console.log('Clean up actions go here');
});
每次 then
或 catch
方法将返回一个新的 Promise
实例,可以继续链接更多的 then
或 catch
方法或直至 finally
方法。这样允许你构建承诺的序列,一步接一步处理异步操作及其结果。
简易封装
/**
* 目标:封装_简易axios函数
* 1. 定义myAxios函数,接收配置对象,返回Promise对象
* 2. 发起XHR请求,默认请求方法为GET
* 3. 调用成功/失败的处理程序
*/
// 1. 定义myAxios函数,接收配置对象,返回Promise对象
function myAxios(config) {
return new Promise((resolve, reject) => {
// 2. 发起XHR请求,默认请求方法为GET
const xhr = new XMLHttpRequest()
xhr.open(config.method || 'GET', config.url)
xhr.addEventListener('loadend', () => {
// 3. 调用成功/失败的处理程序
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
xhr.send()
})
}
如果携带请求参数
function myAxios(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
// 1. 判断有params选项,携带查询参数
if (config.params) {
// 2. 使用URLSearchParams转换,并携带到url上
const paramsObj = new URLSearchParams(config.params)
const queryString = paramsObj.toString()
// 把查询参数字符串,拼接在url?后面
config.url += `?${queryString}`
}
xhr.open(config.method || 'GET', config.url)
xhr.addEventListener('loadend', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
xhr.send()
})
}
修改 myAxios 函数支持传递请求体数据
function myAxios(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
if (config.params) {
const paramsObj = new URLSearchParams(config.params)
const queryString = paramsObj.toString()
config.url += `?${queryString}`
}
xhr.open(config.method || 'GET', config.url)
xhr.addEventListener('loadend', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
// 1. 判断有data选项,携带请求体
if (config.data) {
// 2. 转换数据类型,在send中发送
const jsonStr = JSON.stringify(config.data)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(jsonStr)
} else {
// 如果没有请求体数据,正常的发起请求
xhr.send()
}
})
}