js進(jìn)階教程之promise全方位解析和異步編程
Promise 是 JavaScript 異步編程的核心解決方案,也是前端工程師進(jìn)階必備的核心知識(shí)點(diǎn)。本文將全方位介紹 Promise,涵蓋從基礎(chǔ)概念到高級(jí)應(yīng)用,再到面試實(shí)戰(zhàn)的全鏈路知識(shí)。
一、Promises/A+ 規(guī)范基礎(chǔ)
1. Promise 的狀態(tài)
Promise 是一個(gè)??狀態(tài)機(jī)??,有且僅有三種狀態(tài):
- ??pending(等待中)??:初始狀態(tài),既不是成功也不是失敗。
- ??fulfilled(已成功)??:操作成功完成,通過(guò)
resolve()觸發(fā)。 - ??rejected(已失?。??:操作失敗,通過(guò)
reject()觸發(fā)。
?? ??狀態(tài)不可逆??:一旦從 pending 變?yōu)?fulfilled 或 rejected,狀態(tài)將永久固定,不可再次改變。
2. then 方法:核心機(jī)制
then是 Promise 的核心方法,用于注冊(cè)狀態(tài)變更后的回調(diào)函數(shù):
- ??接收兩個(gè)回調(diào)??:
- 第一個(gè)參數(shù):
onFulfilled(狀態(tài)變?yōu)?fulfilled 時(shí)執(zhí)行)。 - 第二個(gè)參數(shù):
onRejected(狀態(tài)變?yōu)?rejected 時(shí)執(zhí)行,可選)。
- 第一個(gè)參數(shù):
- ?回調(diào)返回值?
若回調(diào)返回普通值(非 Promise),則新 Promise 以該值 fulfilled。
- 若回調(diào)拋出異常,則新 Promise 以該異常 rejected。
- 若回調(diào)返回另一個(gè) Promise,則當(dāng)前 Promise 會(huì)??跟隨該返回 Promise 的狀態(tài)??(鏈?zhǔn)秸{(diào)用的核心)。
const p = new Promise((resolve) => resolve(10));
p.then(
(value) => {
console.log(value); // 10(onFulfilled 回調(diào))
return value * 2; // 返回普通值 → 新 Promise 以 20 fulfilled
},
(err) => console.error(err) // 不會(huì)執(zhí)行(原 Promise 未失?。?
).then((newValue) => {
console.log(newValue); // 20(跟隨返回的隱式 Promise)
});3. Promise 解析過(guò)程(核心機(jī)制)
當(dāng) then的回調(diào)返回一個(gè) ??新的 Promise(記作 P)?? 時(shí),當(dāng)前 Promise 會(huì)暫停自身的狀態(tài)變更,轉(zhuǎn)而??監(jiān)聽(tīng) P 的狀態(tài)??:
- 若 P 變?yōu)?fulfilled,則當(dāng)前 Promise 以 P 的結(jié)果 fulfilled;
- 若 P 變?yōu)?rejected,則當(dāng)前 Promise 以 P 的錯(cuò)誤 rejected。
?? ??鏈?zhǔn)秸{(diào)用的本質(zhì)??:通過(guò)解析過(guò)程實(shí)現(xiàn)多個(gè)異步操作的依次執(zhí)行(前一個(gè)操作的結(jié)果作為下一個(gè)操作的輸入)。
二、傳統(tǒng)異步任務(wù)處理的痛點(diǎn)
1. 回調(diào)函數(shù)模式
早期 JavaScript 通過(guò)嵌套回調(diào)處理異步(例如 AJAX 請(qǐng)求):
getData(function(result1) {
processResult1(result1, function(result2) {
saveResult2(result2, function(result3) {
// 嵌套層級(jí)加深 → 代碼難以維護(hù)
});
});
});2. 回調(diào)地獄(Callback Hell)
多層嵌套回調(diào)會(huì)導(dǎo)致代碼橫向擴(kuò)展(縮進(jìn)層級(jí)過(guò)深),出現(xiàn)“金字塔”結(jié)構(gòu)的代碼,可讀性差、錯(cuò)誤處理困難、難以復(fù)用邏輯。
?? ??Promise 的誕生??:為了解決回調(diào)地獄問(wèn)題,提供更扁平化的異步代碼組織方式。
三、Promise 詳解
1. 什么是 Promise?
- ??本質(zhì)??:一個(gè)構(gòu)造函數(shù)(類(lèi)),通過(guò)
new Promise(executor)實(shí)例化。 - 核心機(jī)制??:
- executor 是一個(gè)同步執(zhí)行的回調(diào)函數(shù),接收兩個(gè)參數(shù):
resolve(標(biāo)記成功)和reject(標(biāo)記失?。?。 - Promise 內(nèi)部通過(guò)狀態(tài)機(jī)管理異步操作的結(jié)果,外部通過(guò)
then/catch/finally監(jiān)聽(tīng)狀態(tài)變更。
- executor 是一個(gè)同步執(zhí)行的回調(diào)函數(shù),接收兩個(gè)參數(shù):
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('異步操作成功'), 1000); // 1秒后標(biāo)記成功
});2. resolve 的參數(shù)規(guī)則
resolve可接受多種類(lèi)型的參數(shù),處理邏輯不同:
參數(shù)類(lèi)型 | 行為 |
|---|---|
普通值(如字符串、數(shù)字) | 當(dāng)前 Promise 以該值 fulfilled。 |
另一個(gè) Promise | 當(dāng)前 Promise 會(huì)跟隨該 Promise 的狀態(tài)(鏈?zhǔn)秸{(diào)用的基礎(chǔ))。 |
Thenable 對(duì)象(含 then 方法的對(duì)象) | 會(huì)嘗試調(diào)用其 then 方法,將其轉(zhuǎn)換為標(biāo)準(zhǔn) Promise 行為。 |
// 示例 1:普通值
Promise.resolve(42).then(v => console.log(v)); // 42
// 示例 2:返回另一個(gè) Promise
Promise.resolve(Promise.resolve('嵌套 Promise')).then(v => console.log(v)); // 嵌套 Promise
// 示例 3:Thenable 對(duì)象
const thenable = { then(resolve) { resolve('Thenable 轉(zhuǎn)換成功'); } };
Promise.resolve(thenable).then(v => console.log(v)); // Thenable 轉(zhuǎn)換成功3. Promise 的實(shí)例方法
(1) then 方法
- ??參數(shù)??:
onFulfilled(成功回調(diào),可選)、onRejected(失敗回調(diào),可選)。 - ??返回值??:一個(gè)新的 Promise(支持鏈?zhǔn)秸{(diào)用)。
- ??回調(diào)返回值??:決定新 Promise 的狀態(tài)(如返回普通值、Promise 或拋出異常)。
Promise.resolve(1) .then(val => val + 1) // 返回普通值 2 → 新 Promise 以 2 fulfilled .then(val => console.log(val)); // 2
(2) 多次調(diào)用 then
- 每次調(diào)用
then都會(huì)注冊(cè)新的回調(diào),??按注冊(cè)順序依次執(zhí)行??(即使前一個(gè)回調(diào)異步延遲)。 - 多個(gè)
then可以分別處理同一 Promise 的成功狀態(tài)。
const p = Promise.resolve(10); p.then(v => console.log(v)); // 10 p.then(v => console.log(v + 5)); // 15
(3) catch 方法
- ??作用??:專(zhuān)門(mén)捕獲 Promise 鏈中的錯(cuò)誤(相當(dāng)于
then(null, onRejected))。 - ??參數(shù)??:錯(cuò)誤處理回調(diào)(接收錯(cuò)誤對(duì)象)。
- ??特性??:可捕獲當(dāng)前 Promise 或鏈中上游未處理的 rejected 狀態(tài)。
Promise.reject(new Error('操作失敗'))
.catch(err => console.error('捕獲錯(cuò)誤:', err.message)); // 捕獲錯(cuò)誤: 操作失敗(4) finally 方法
- ??作用??:無(wú)論 Promise 成功或失敗,都會(huì)執(zhí)行的清理邏輯(如關(guān)閉加載動(dòng)畫(huà))。
- ??參數(shù)??:無(wú)參數(shù)回調(diào)(無(wú)法獲取 Promise 的結(jié)果或錯(cuò)誤)。
- ??特性??:不會(huì)改變?cè)?Promise 的狀態(tài)或結(jié)果。
Promise.resolve('成功')
.finally(() => console.log('無(wú)論如何都會(huì)執(zhí)行')) // 無(wú)論如何都會(huì)執(zhí)行
.then(v => console.log(v)); // 成功4. Promise 的類(lèi)方法
(1) Promise.resolve / Promise.reject
- ??作用??:快速創(chuàng)建已 resolved/rejected 的 Promise。
- ??參數(shù)??:與
resolve/reject方法一致。
Promise.resolve('直接成功').then(v => console.log(v)); // 直接成功
Promise.reject('直接失敗').catch(e => console.error(e)); // 直接失敗(2) Promise.all
- ??作用??:并行執(zhí)行多個(gè) Promise,??全部成功時(shí)返回結(jié)果數(shù)組??,??任一失敗立即 rejected??。
- ??參數(shù)??:Promise 數(shù)組。
- 適用場(chǎng)景??:同時(shí)發(fā)起多個(gè)獨(dú)立異步請(qǐng)求,需全部完成后再處理結(jié)果。
Promise.all([
Promise.resolve(1),
Promise.resolve(2),
]).then(([res1, res2]) => console.log(res1, res2)); // 1 2
Promise.all([
Promise.resolve(1),
Promise.reject('失敗'),
]).catch(e => console.error(e)); // 失敗(3) Promise.allSettled
- 作用??:并行執(zhí)行多個(gè) Promise,??等待所有完成(無(wú)論成功/失?。??,返回每個(gè) Promise 的狀態(tài)和結(jié)果。
- ??參數(shù)??:Promise 數(shù)組。
- ??適用場(chǎng)景??:需要知道所有異步操作的最終狀態(tài)(如批量提交后統(tǒng)計(jì)成功/失敗數(shù)量)。
Promise.allSettled([
Promise.resolve(1),
Promise.reject('失敗'),
]).then(results => {
results.forEach(r => console.log(r.status, r.value || r.reason));
// fulfilled 1
// rejected 失敗
});(4) Promise.race
- ??作用??:并行執(zhí)行多個(gè) Promise,??返回第一個(gè) settled(成功/失?。┑?Promise 結(jié)果??。
- ??參數(shù)??:Promise 數(shù)組。
- 適用場(chǎng)景??:超時(shí)控制(如請(qǐng)求超時(shí)后取消操作)。
Promise.race([
Promise.resolve('快的'),
new Promise(resolve => setTimeout(() => resolve('慢的'), 1000)),
]).then(v => console.log(v)); // 快的(5) Promise.any
- ??作用??:并行執(zhí)行多個(gè) Promise,??返回第一個(gè)成功的 Promise 結(jié)果??,??全部失敗時(shí)拋出 AggregateError??。
- ??參數(shù)??:Promise 數(shù)組。
- ??適用場(chǎng)景??:多備用數(shù)據(jù)源(如主接口失敗后嘗試備用接口)。
Promise.any([
Promise.reject('失敗1'),
Promise.resolve('成功'),
]).then(v => console.log(v)); // 成功四、手寫(xiě) Promise 實(shí)現(xiàn)(核心邏輯)
手寫(xiě) Promise 是深入理解其原理的最佳方式,需實(shí)現(xiàn)以下核心方法(可通過(guò) Jest 編寫(xiě)單元測(cè)試驗(yàn)證):
- ??構(gòu)造函數(shù)(constructor)??:接收 executor,管理狀態(tài)和結(jié)果。
- ??then/catch/finally??:注冊(cè)回調(diào)并處理鏈?zhǔn)秸{(diào)用。
- 類(lèi)方法(resolve/reject/all/allSettled/race/any)??:靜態(tài)工具方法。
?? ??關(guān)鍵點(diǎn)??:狀態(tài)不可逆、then 的鏈?zhǔn)秸{(diào)用(返回新 Promise)、異步執(zhí)行回調(diào)(通過(guò)微任務(wù)隊(duì)列,如
queueMicrotask)。
下面是一個(gè)完整的手寫(xiě) Promise 實(shí)現(xiàn)(支持 Promises/A+ 規(guī)范),包含核心方法(constructor、then、catch、finally)及靜態(tài)方法(resolve、reject、all、allSettled、race、any),并附帶對(duì)應(yīng)的 Jest 單元測(cè)試代碼。
1.Promise 實(shí)現(xiàn) (myPromise.js)
// 定義 Promise 的三種狀態(tài)
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
this.state = PENDING; // 初始狀態(tài)為 pending
this.value = undefined; // 成功時(shí)的值
this.reason = undefined; // 失敗時(shí)的原因
this.onFulfilledCallbacks = []; // 成功的回調(diào)隊(duì)列
this.onRejectedCallbacks = []; // 失敗的回調(diào)隊(duì)列
// 定義 resolve 函數(shù)
const resolve = (value) => {
// 只有 pending 狀態(tài)可以改變
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
// 執(zhí)行所有成功的回調(diào)
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
// 定義 reject 函數(shù)
const reject = (reason) => {
// 只有 pending 狀態(tài)可以改變
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
// 執(zhí)行所有失敗的回調(diào)
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
// 立即執(zhí)行 executor
executor(resolve, reject);
} catch (error) {
// 如果 executor 執(zhí)行出錯(cuò),直接 reject
reject(error);
}
}
// then 方法
then(onFulfilled, onRejected) {
// 處理 then 的參數(shù)不是函數(shù)的情況
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
// 返回一個(gè)新的 Promise 以實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用
const promise2 = new MyPromise((resolve, reject) => {
// 如果狀態(tài)是 fulfilled
if (this.state === FULFILLED) {
// 使用 setTimeout 模擬微任務(wù)
setTimeout(() => {
try {
const x = onFulfilled(this.value);
// 解析 promise2 和 x 的關(guān)系
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
} else if (this.state === REJECTED) {
// 如果狀態(tài)是 rejected
setTimeout(() => {
try {
const x = onRejected(this.reason);
// 解析 promise2 和 x 的關(guān)系
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
} else if (this.state === PENDING) {
// 如果狀態(tài)是 pending,將回調(diào)函數(shù)存入隊(duì)列
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return promise2;
}
// 解析 promise 和 x 的關(guān)系
resolvePromise(promise2, x, resolve, reject) {
// 如果 promise2 和 x 是同一個(gè)對(duì)象,拋出 TypeError
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 如果 x 是一個(gè) Promise
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else {
// 如果 x 是一個(gè)普通值
resolve(x);
}
}
// catch 方法
catch(onRejected) {
return this.then(null, onRejected);
}
// finally 方法
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason; })
);
}
// 靜態(tài) resolve 方法
static resolve(value) {
// 如果 value 是一個(gè) Promise,直接返回
if (value instanceof MyPromise) {
return value;
}
// 否則返回一個(gè)新的 resolved Promise
return new MyPromise(resolve => resolve(value));
}
// 靜態(tài) reject 方法
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
// 靜態(tài) all 方法
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let completed = 0;
const promiseCount = promises.length;
if (promiseCount === 0) {
resolve(results);
return;
}
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
results[index] = value;
completed++;
if (completed === promiseCount) {
resolve(results);
}
},
reason => {
reject(reason);
}
);
});
});
}
// 靜態(tài) allSettled 方法
static allSettled(promises) {
return new MyPromise((resolve) => {
const results = [];
let completed = 0;
const promiseCount = promises.length;
if (promiseCount === 0) {
resolve(results);
return;
}
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
results[index] = { status: FULFILLED, value };
completed++;
if (completed === promiseCount) {
resolve(results);
}
},
reason => {
results[index] = { status: REJECTED, reason };
completed++;
if (completed === promiseCount) {
resolve(results);
}
}
);
});
});
}
// 靜態(tài) race 方法
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
MyPromise.resolve(promise).then(resolve, reject);
});
});
}
// 靜態(tài) any 方法
static any(promises) {
return new MyPromise((resolve, reject) => {
const errors = [];
let rejectedCount = 0;
const promiseCount = promises.length;
if (promiseCount === 0) {
reject(new AggregateError(errors, 'All promises were rejected'));
return;
}
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
resolve(value);
},
reason => {
errors[index] = reason;
rejectedCount++;
if (rejectedCount === promiseCount) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
}
);
});
});
}
}
module.exports = MyPromise;2.Jest 單元測(cè)試 (myPromise.test.js)
const MyPromise = require('./myPromise');
describe('MyPromise', () => {
// 測(cè)試構(gòu)造函數(shù)和基本功能
describe('Constructor', () => {
test('should execute executor immediately', () => {
let executed = false;
new MyPromise(() => {
executed = true;
});
expect(executed).toBe(true);
});
test('should handle resolve in executor', (done) => {
new MyPromise((resolve) => {
resolve('success');
}).then((value) => {
expect(value).toBe('success');
done();
});
});
test('should handle reject in executor', (done) => {
new MyPromise((_, reject) => {
reject('error');
}).catch((reason) => {
expect(reason).toBe('error');
done();
});
});
test('should catch executor errors', (done) => {
new MyPromise(() => {
throw new Error('executor error');
}).catch((error) => {
expect(error.message).toBe('executor error');
done();
});
});
});
// 測(cè)試 then 方法
describe('then', () => {
test('should handle fulfilled promise', (done) => {
new MyPromise((resolve) => {
resolve('fulfilled');
}).then((value) => {
expect(value).toBe('fulfilled');
done();
});
});
test('should handle rejected promise', (done) => {
new MyPromise((_, reject) => {
reject('rejected');
}).then(null, (reason) => {
expect(reason).toBe('rejected');
done();
});
});
test('should chain then calls', (done) => {
new MyPromise((resolve) => {
resolve(1);
})
.then((value) => {
expect(value).toBe(1);
return value + 1;
})
.then((value) => {
expect(value).toBe(2);
return value + 1;
})
.then((value) => {
expect(value).toBe(3);
done();
});
});
test('should handle async then callbacks', (done) => {
let first = false;
new MyPromise((resolve) => {
resolve(1);
})
.then((value) => {
first = true;
return value + 1;
})
.then((value) => {
expect(first).toBe(true);
expect(value).toBe(2);
done();
});
});
test('should use default onFulfilled if not a function', (done) => {
new MyPromise((resolve) => {
resolve(1);
}).then(null).then((value) => {
expect(value).toBe(1);
done();
});
});
test('should use default onRejected if not a function', (done) => {
new MyPromise((_, reject) => {
reject(1);
}).then(null, null).catch((reason) => {
expect(reason).toBe(1);
done();
});
});
});
// 測(cè)試 catch 方法
describe('catch', () => {
test('should catch rejected promise', (done) => {
new MyPromise((_, reject) => {
reject('error');
}).catch((reason) => {
expect(reason).toBe('error');
done();
});
});
test('should chain catch calls', (done) => {
new MyPromise((_, reject) => {
reject('first error');
})
.catch((reason) => {
expect(reason).toBe('first error');
return 'recovered';
})
.then((value) => {
expect(value).toBe('recovered');
done();
});
});
});
// 測(cè)試 finally 方法
describe('finally', () => {
test('should execute finally callback', (done) => {
let called = false;
new MyPromise((resolve) => {
resolve('value');
})
.finally(() => {
called = true;
})
.then((value) => {
expect(called).toBe(true);
expect(value).toBe('value');
done();
});
});
test('should pass through value', (done) => {
new MyPromise((resolve) => {
resolve('value');
})
.finally(() => {})
.then((value) => {
expect(value).toBe('value');
done();
});
});
test('should pass through reason', (done) => {
new MyPromise((_, reject) => {
reject('reason');
})
.finally(() => {})
.catch((reason) => {
expect(reason).toBe('reason');
done();
});
});
test('should handle finally callback with return value', (done) => {
new MyPromise((resolve) => {
resolve(1);
})
.finally(() => {
return 2;
})
.then((value) => {
expect(value).toBe(1);
done();
});
});
test('should handle finally callback with promise', (done) => {
new MyPromise((resolve) => {
resolve(1);
})
.finally(() => {
return new MyPromise((resolve) => {
resolve(2);
});
})
.then((value) => {
expect(value).toBe(1);
done();
});
});
});
// 測(cè)試靜態(tài)方法 resolve
describe('static resolve', () => {
test('should resolve with a value', (done) => {
MyPromise.resolve('value').then((value) => {
expect(value).toBe('value');
done();
});
});
test('should resolve with a promise', (done) => {
const p = new MyPromise((resolve) => {
resolve('promise value');
});
MyPromise.resolve(p).then((value) => {
expect(value).toBe('promise value');
done();
});
});
});
// 測(cè)試靜態(tài)方法 reject
describe('static reject', () => {
test('should reject with a reason', (done) => {
MyPromise.reject('reason').catch((reason) => {
expect(reason).toBe('reason');
done();
});
});
});
// 測(cè)試靜態(tài)方法 all
describe('static all', () => {
test('should resolve with an array of values', (done) => {
const p1 = MyPromise.resolve(1);
const p2 = MyPromise.resolve(2);
const p3 = MyPromise.resolve(3);
MyPromise.all([p1, p2, p3]).then((values) => {
expect(values).toEqual([1, 2, 3]);
done();
});
});
test('should reject if any promise rejects', (done) => {
const p1 = MyPromise.resolve(1);
const p2 = MyPromise.reject('error');
const p3 = MyPromise.resolve(3);
MyPromise.all([p1, p2, p3]).catch((reason) => {
expect(reason).toBe('error');
done();
});
});
test('should resolve with empty array', (done) => {
MyPromise.all([]).then((values) => {
expect(values).toEqual([]);
done();
});
});
});
// 測(cè)試靜態(tài)方法 allSettled
describe('static allSettled', () => {
test('should resolve with all settled results', (done) => {
const p1 = MyPromise.resolve(1);
const p2 = MyPromise.reject('error');
const p3 = MyPromise.resolve(3);
MyPromise.allSettled([p1, p2, p3]).then((results) => {
expect(results).toEqual([
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: 'error' },
{ status: 'fulfilled', value: 3 }
]);
done();
});
});
test('should resolve with empty array', (done) => {
MyPromise.allSettled([]).then((results) => {
expect(results).toEqual([]);
done();
});
});
});
// 測(cè)試靜態(tài)方法 race
describe('static race', () => {
test('should resolve with the first resolved promise', (done) => {
const p1 = new MyPromise((resolve) => {
setTimeout(() => resolve(1), 100);
});
const p2 = MyPromise.resolve(2);
MyPromise.race([p1, p2]).then((value) => {
expect(value).toBe(2);
done();
});
});
test('should reject with the first rejected promise', (done) => {
const p1 = new MyPromise((_, reject) => {
setTimeout(() => reject('error'), 100);
});
const p2 = MyPromise.reject('quick error');
MyPromise.race([p1, p2]).catch((reason) => {
expect(reason).toBe('quick error');
done();
});
});
});
// 測(cè)試靜態(tài)方法 any
describe('static any', () => {
test('should resolve with the first resolved promise', (done) => {
const p1 = new MyPromise((resolve) => {
setTimeout(() => resolve(1), 100);
});
const p2 = MyPromise.resolve(2);
MyPromise.any([p1, p2]).then((value) => {
expect(value).toBe(2);
done();
});
});
test('should reject with all rejected promises if all are rejected', (done) => {
const p1 = MyPromise.reject('error1');
const p2 = MyPromise.reject('error2');
MyPromise.any([p1, p2]).catch((error) => {
expect(error.errors).toEqual(['error1', 'error2']);
expect(error.message).toBe('All promises were rejected');
done();
});
});
test('should reject with AggregateError if all are rejected', (done) => {
const p1 = MyPromise.reject('error1');
const p2 = MyPromise.reject('error2');
MyPromise.any([p1, p2]).catch((error) => {
expect(error instanceof Error).toBe(true);
expect(error.message).toBe('All promises were rejected');
// 注意:這里簡(jiǎn)化了 AggregateError 的實(shí)現(xiàn),實(shí)際可能需要更復(fù)雜的檢查
done();
});
});
});
});使用說(shuō)明
- 將 Promise 實(shí)現(xiàn)代碼保存為
myPromise.js - 將測(cè)試代碼保存為
myPromise.test.js - 確保已安裝 Jest (
npm install --save-dev jest) - 在 package.json 中添加測(cè)試腳本:
"scripts": {
"test": "jest"
} 5. 運(yùn)行測(cè)試:npm test
實(shí)現(xiàn)說(shuō)明
這個(gè) Promise 實(shí)現(xiàn)包含了以下特性:
- 基本功能??:
- 三種狀態(tài):pending、fulfilled、rejected
- 構(gòu)造函數(shù)接收 executor 函數(shù)
- 異步執(zhí)行回調(diào)(使用 setTimeout 模擬微任務(wù))
- 實(shí)例方法??:
then(): 支持鏈?zhǔn)秸{(diào)用,處理成功和失敗情況catch(): 捕獲錯(cuò)誤finally(): 無(wú)論成功失敗都會(huì)執(zhí)行
- ??靜態(tài)方法??:
resolve(): 創(chuàng)建一個(gè)已解決的 Promisereject(): 創(chuàng)建一個(gè)已拒絕的 Promiseall(): 所有 Promise 都成功時(shí)返回結(jié)果數(shù)組allSettled(): 所有 Promise 都完成后返回結(jié)果狀態(tài)數(shù)組race(): 返回第一個(gè)完成的 Promise 結(jié)果any(): 返回第一個(gè)成功的 Promise,或所有都失敗時(shí)拋出錯(cuò)誤
五、async/await 介紹
1. 特點(diǎn)
- ??語(yǔ)法糖??:基于 Promise 的語(yǔ)法糖,讓異步代碼看起來(lái)像同步代碼。
- 本質(zhì)??:
async函數(shù)返回 Promise,await用于暫停執(zhí)行直到 Promise 完成。
2. 用法
async function fetchData() {
try {
const result = await Promise.resolve('異步數(shù)據(jù)'); // 等待 Promise 完成
console.log(result); // 異步數(shù)據(jù)
} catch (err) {
console.error(err);
}
}
fetchData();3. 適用場(chǎng)景
- 需要按順序執(zhí)行多個(gè)異步操作(如先請(qǐng)求用戶(hù)信息,再請(qǐng)求訂單詳情)。
- 替代復(fù)雜的 Promise 鏈(提升代碼可讀性)。
4. 與 Promise 對(duì)比
特性 | async/await | Promise |
|---|---|---|
代碼風(fēng)格 | 類(lèi)似同步代碼,更直觀(guān) | 鏈?zhǔn)秸{(diào)用,需嵌套 then/catch |
錯(cuò)誤處理 | 使用 try/catch | 使用 catch 方法 |
可讀性 | 高(邏輯線(xiàn)性) | 較低(嵌套層級(jí)深時(shí)) |
底層原理 | 基于 Promise | JavaScript 原生異步解決方案 |
六、Promise 相關(guān)面試題目
1. 經(jīng)典面試題:Promise 與事件循環(huán)
??題目??:分析以下代碼的輸出順序(結(jié)合宏任務(wù)與微任務(wù)):
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');解題思路??:
- 同步代碼優(yōu)先執(zhí)行??:輸出
1→4。 - ??微任務(wù)(Promise.then)??:在當(dāng)前宏任務(wù)結(jié)束后立即執(zhí)行,輸出
3。 - ??宏任務(wù)(setTimeout)??:在下一個(gè)事件循環(huán)中執(zhí)行,輸出
2。
??最終輸出順序??:1 → 4 → 3 → 2。
2. 事件循環(huán)(宏任務(wù)與微任務(wù))
- ??宏任務(wù)??:script 整體代碼、setTimeout、setInterval、I/O 操作。
- ?微任務(wù)??:Promise.then/catch/finally、MutationObserver、queueMicrotask。
??執(zhí)行規(guī)則??:
- 執(zhí)行一個(gè)宏任務(wù)(如 script 代碼);
- 執(zhí)行該宏任務(wù)產(chǎn)生的所有微任務(wù);
- 渲染頁(yè)面(如有必要);
- 執(zhí)行下一個(gè)宏任務(wù)。
3. 解題思路總結(jié)
- Promise 狀態(tài)機(jī)??:明確 pending → fulfilled/rejected 的不可逆性。
- ??then 的鏈?zhǔn)秸{(diào)用??:每次 then 返回新 Promise,回調(diào)返回值決定新?tīng)顟B(tài)。
- ??事件循環(huán)優(yōu)先級(jí)??:微任務(wù) > 宏任務(wù)(理解這一規(guī)則可解決 80% 的異步面試題)。
通過(guò)本文的全方位解析,你已掌握 Promise 的核心原理、實(shí)戰(zhàn)用法及面試要點(diǎn)。結(jié)合實(shí)際項(xiàng)目中的異步場(chǎng)景(如 API 請(qǐng)求、文件讀取),靈活運(yùn)用 Promise 和 async/await,可顯著提升代碼質(zhì)量和開(kāi)發(fā)效率! ??
到此這篇關(guān)于js進(jìn)階教程之promise全方位解析和異步編程的文章就介紹到這了,更多相關(guān)js promise解析和異步編程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript 獲取網(wǎng)頁(yè)參數(shù)系統(tǒng)
用處比較多,適合在當(dāng)前網(wǎng)頁(yè)打開(kāi)別的網(wǎng)站的內(nèi)容2008-07-07
Javascript驗(yàn)證上傳圖片大小[前臺(tái)處理]
在做上傳圖片的時(shí)候,如果不限制上傳圖片大小,后果非常的嚴(yán)重。解決這個(gè)問(wèn)題有兩種方式:后臺(tái)處理、前臺(tái)處理2014-07-07
js數(shù)字計(jì)算 誤差問(wèn)題的快速解決方法
下面小編就為大家?guī)?lái)一篇js數(shù)字計(jì)算 誤差問(wèn)題的快速解決方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02
javascript數(shù)據(jù)結(jié)構(gòu)之串的概念與用法分析
這篇文章主要介紹了javascript數(shù)據(jù)結(jié)構(gòu)之串的概念與用法,簡(jiǎn)單講述了串的概念、功能并結(jié)合實(shí)例形式分析了基于javascript實(shí)現(xiàn)串的遍歷、比較、查找等相關(guān)操作技巧,需要的朋友可以參考下2017-04-04
js判斷數(shù)據(jù)類(lèi)型如判斷是否為數(shù)組是否為字符串等等
js判斷數(shù)據(jù)類(lèi)型如判斷是否為數(shù)組類(lèi)型、判斷是否為字符串類(lèi)型、判斷是否為數(shù)值類(lèi)型等等,本文有幾個(gè)不錯(cuò)的示例,大家可以學(xué)習(xí)下2014-01-01
Javascript刪除指定元素節(jié)點(diǎn)的方法
這篇文章主要介紹了使用Javascript刪除指定元素節(jié)點(diǎn)的方法,通俗易懂,需要的朋友可以參考下。2016-06-06
Promise對(duì)象all與race方法手寫(xiě)示例
這篇文章主要為大家介紹了Promise對(duì)象all與race方法手寫(xiě)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12

