Web前端入門第 69 問:JavaScript Promise 提供的方法都使用過嗎?
Promise 這個 API 曾在 JS 領(lǐng)域掀起過血雨腥風,以前的大佬們都喜歡手搓一個自己的 Promise 用以理解 Promise 的原理。
Promise 的誕生,應該多少都有受到 jQuery 的異步方法 $.Deferred() 影響。
應用場景
Promise 唯一作用就是在處理異步耗時任務的時候,不要出現(xiàn)回調(diào)地獄。在沒有 Promise 之前,一般使用 callback 來解決異步問題,一般代碼都是這樣:
a(() => {
b(() => {
c(() => {
d(() => {})
})
})
})
就這樣一層一層套進去,像套娃一樣的回調(diào)方法,這就是所謂的 回調(diào)地獄。
使用 Promise 之后,代碼就可以改成鏈式調(diào)用,避免了回調(diào)地獄:
a()
.then(() => {
return b()
}).then(() => {
return c()
}).then(() => {
return d()
})
常見用法
在代碼中經(jīng)常會看到這樣使用 Promise:
function task() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('耗時任務執(zhí)行成功')
}, 1000)
})
}
// 開始執(zhí)行耗時任務
task().then(res => {
console.log('任務執(zhí)行成功')
}).catch(err => {
console.log('任務執(zhí)行失敗')
}).finally(() => {
console.log('任務執(zhí)行完成')
})
// 或者是這樣
task().then(res => {
console.log('任務執(zhí)行成功')
}, err => {
console.log('任務執(zhí)行失敗')
}).finally(() => {
console.log('任務執(zhí)行完成')
})
.then 方法接收兩個參數(shù),第一個參數(shù)是執(zhí)行成功(fulfilled)的回調(diào)方法,第二個參數(shù)是執(zhí)行失敗(rejected)的回調(diào)方法。
.catch 方法用于捕獲 Promise 中的錯誤,如果 Promise 執(zhí)行失敗,就會執(zhí)行 .catch 方法,比如:
function task() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('任務執(zhí)行失敗'))
}, 1000)
})
}
task().catch(err => {
console.log(err.message)
})
.finally 方法無論 Promise 執(zhí)行成功與否,都會執(zhí)行的方法,一般多用于關(guān)閉 loading 這種效果,也可以用于清理資源。
Promise 的靜態(tài)方法
以上三個方法都是 Promise 的實例方法,除了常用的實例方法外,Promise 還提供了一些靜態(tài)方法,這些靜態(tài)方法不是很常用(也可能是咱的段位太低),但在某些特定的需求場景中也是很有用的利器。
Promise.reject() 與 Promise.resolve()
這一對靜態(tài)方法一般多用于將同步方法改成 Promise,比如:
function task() {
if (Math.random() > 0.5) {
return Promise.reject(new Error('任務執(zhí)行失敗'))
}
return Promise.resolve('任務執(zhí)行成功')
}
task().then((res) => console.log(res), err => console.error(err.message))
其參數(shù)還支持返回一個 Promise 對象或者一個 thenable 對象。比如這樣:
function task1() {
return new Promise((resolve, reject) => {
if (Math.random() > 0.5) {
return resolve('耗時任務執(zhí)行成功')
}
return reject(new Error('任務執(zhí)行失敗'))
})
}
function task2() {
// Promise 對象
return Promise.resolve(task1())
}
task2().then((res) => console.log(res), err => console.error(err.message))
// ----------------------
function task3() {
// thenable 對象
const thenable = {
then(onFulfill, onReject) {
if (Math.random() > 0.5) {
return onReject(new Error('任務執(zhí)行失敗'))
}
return onFulfill('任務執(zhí)行成功')
},
}
return Promise.resolve(thenable)
}
task3().then((res) => console.log(res), err => console.error(err.message))
Promise.all()
用于同時處理多個 Promise,如果全部都成功解決時,返回的 Promise 才會解決,但凡有一個被拒絕,則返回的 Promise 失敗。
const p1 = Promise.resolve('1')
const p2 = Promise.reject('2')
const p3 = Promise.resolve('3')
Promise.all([p1, p3]).then(res => {
console.log('成功', res) // ['1', '3']
}).catch(err => {
console.error('失敗', err)
})
Promise.all([p2, p3]).then(res => {
console.log('成功', res)
}).catch(err => {
console.error('失敗', err) // 獲得失敗的返回值 2
})
Promise.allSettled()
與 Promise.all 有點不同,這個靜態(tài)方法會等到所有的 Promise 都解決或者失敗,然后返回一個 Promise,這個 Promise 的結(jié)果是一個數(shù)組,數(shù)組的元素是所有 Promise 的狀態(tài)及響應結(jié)果。一般多用于多個接口同時請求場景,可以容忍部分接口異常的情況。
const p1 = Promise.resolve('1')
const p2 = Promise.reject('用于測試失敗')
const p3 = Promise.resolve('3')
Promise.allSettled([p1, p2, p3]).then(res => {
res.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value);
} else {
console.error('失敗:', result.reason);
}
});
})
Promise.any()
也是用于處理多個 Promise,此方法的邏輯是:只獲取第一個成功的 Promise 返回結(jié)果,如果全部失敗,則返回一個失敗的 Promise。
const p1 = new Promise((resolve, reject) => setTimeout(reject, 100, '第一個失敗'));
const p2 = new Promise((resolve) => setTimeout(resolve, 200, '第二個成功'));
const p3 = new Promise((resolve, reject) => setTimeout(reject, 300, '第三個失敗'));
const p4 = new Promise((resolve) => setTimeout(resolve, 200, '第四個成功'));
Promise.any([p1, p2, p3, p4]).then(res => console.log('成功:', res)); // 成功: 第二個成功
Promise.any([p1, p3]).catch(error => {
console.error('所有 Promise 失敗:', error) // AggregateError: All promises were rejected
console.error('失敗原因:', error.errors) // ['第一個失敗', '第三個失敗']
});
Promise.race()
此方法存在競速的邏輯,誰最快返回就獲得誰的結(jié)果,不論此結(jié)果是成功還是失敗。
const p1 = new Promise((resolve, reject) => setTimeout(reject, 100, '第一個失敗'));
const p2 = new Promise((resolve) => setTimeout(resolve, 100, '第二個成功'));
Promise.race([p1, p2])
.then(res => console.log('成功:', res))
.catch(error => console.error('失敗:', error)); // 失敗: 第一個失敗
// p2 p1 交換位置,就會獲得成功的結(jié)果
Promise.race([p2, p1])
.then(res => console.log('成功:', res)) // 成功: 第二個成功
.catch(error => {
console.error('失敗:', error)
});
Promise.withResolvers()
2024 年新增的規(guī)范,使用時需注意兼容情況。
這方法相當于封裝了一個語法糖,想比之前擁有了更簡潔的代碼邏輯而已,一般多用于跨模塊共享 Promise 狀態(tài)。使用方法:
const { promise, resolve, reject } = Promise.withResolvers();
function task() {
if (Math.random() > 0.5) {
return resolve('任務執(zhí)行成功')
}
return reject(new Error('任務執(zhí)行失敗'))
}
task()
promise.then(res => {
console.log('成功:', res)
}).catch(err => {
console.error('失敗:', err)
})
只是需要特別注意,promise 的狀態(tài)在變?yōu)橐呀鉀Q或失敗時,promise 的狀態(tài)就無法再修改了,后面再調(diào)用 resolve 或 reject 方法都無任何響應。
這個靜態(tài)方法可使用原有的方法實現(xiàn),如下:
function createDeferredPromise() {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
const { promise, resolve, reject } = createDeferredPromise();
想比而言,withResolvers實現(xiàn)的代碼更加簡潔。
Promise.try()
這方法可時髦了,2025年才新增的規(guī)范,使用時需特別小心兼容性。
跟 try catch 相似,都是用于捕獲異常,使用方法:
console.log(1)
Promise.try(() => {
console.log(3)
throw new Error('前端路引')
}).catch(err => console.log('捕獲:', err))
console.log(2)
執(zhí)行順序:
1
3
2
捕獲: Error: 前端路引
關(guān)于兼容性
由于 Promise 的靜態(tài)方法都是在不同的 ES 版本迭代時添加進來的規(guī)范,所以多多少少都有一些兼容問題,在 Vite 項目中,可以使用以下兩個插件來處理兼容問題:
1、@vitejs/plugin-legacy
周下載量在 35萬左右
npm 地址:https://www.npmjs.com/package/@vitejs/plugin-legacy
2、vite-plugin-legacy-swc
周下載量再 1萬左右
npm 地址:https://www.npmjs.com/package/vite-plugin-legacy-swc
使用方法可以參考 npm 的 Readme 文檔。
寫在最后
Promise 在處理異步任務時特別常用,還多用于一些耗時太長的任務場景,掌握 Promise 的使用,有利于編寫出易于維護的項目代碼。
文章首發(fā)于微信公眾號【前端路引】,歡迎 微信掃一掃 查看更多文章。
本文來自博客園,作者:前端路引,轉(zhuǎn)載請注明原文鏈接:http://www.rzrgm.cn/linx/p/18945622

浙公網(wǎng)安備 33010602011771號