再有人问Promise,我就用这 9 个高级用法闪瞎他的眼!
概述
想象一下你点外卖:下单后,你并不知道外卖什么时候送达,也不知道会不会出问题。 Promise 就类似于这个外卖订单的“状态记录器”。它代表着一个异步操作(比如网络请求、文件读取)的最终结果。
这个“状态记录器”总共有三种状态:
- 等待中 (Pending): 就像你刚下单,外卖还在准备中,不知道最终结果如何。
- 已完成 (Fulfilled): 外卖送到了,你成功拿到餐食了!
- 已拒绝 (Rejected): 外卖出问题了,比如商家缺货、配送员出了意外,你没拿到外卖。
传统的回调函数处理异步操作就像你一直盯着手机等外卖,还要处理各种可能的情况(例如:超时、商家联系不上等等)。而Promise就像一个更高级的“外卖监控系统”,它让你不用一直盯着,系统会自动通知你外卖的状态变化:
- 它会在事件循环完成后才通知你结果(也就是你不会一直刷新页面查看状态)。
- 即使外卖送达(无论成功或失败),你之前添加的“监控”
then()
仍然会收到通知。 - 你可以添加多个“监控”,它们会按照添加顺序依次收到通知。
Promise 最强大的功能是链式调用,就像你可以把外卖送达后“吃午餐”,“洗碗”这些步骤串联起来一样,一个步骤完成后自动执行下一个步骤,让整个流程变得清晰流畅。
常见方法
Promise 提供了几个常用的静态方法来方便地创建和操作多个 Promise:
-
Promise.all(promises)
: 同时等待多个 Promise 完成,只有全部完成才返回结果,任何一个失败则整个操作失败。 就像同时点了几家外卖,只有全部送达你才能开始吃饭。 -
Promise.allSettled(promises)
: 等待所有 Promise 完成,无论成功还是失败都返回结果,就像你点了几家外卖,无论哪家出了问题,你都会知道结果。 -
Promise.any(promises)
: 等待多个 Promise 中的任意一个完成,只要有一个成功就返回结果,全部失败才算失败。 就像你同时点了几家外卖,只要有一家送达,你就可以先吃。 -
Promise.race(promises)
: 等待多个 Promise 中最先完成的一个,无论成功还是失败。 就像你同时点了几家外卖,先到的一家决定你的用餐时间。 -
Promise.resolve(value)
: 创建一个已完成的Promise,直接返回value。就像外卖已经提前送到。 -
Promise.reject(reason)
: 创建一个已拒绝的Promise,直接返回reason。就像外卖送餐途中出事了。
此外,Promise 实例本身也有一些重要的方法,例如 then()
用于添加成功回调,catch()
用于添加失败回调,finally()
无论成功失败都会执行的回调。 这些方法共同构成了 Promise 的核心功能,提供了处理异步操作的强大机制。
基础用法
1. Promise.all([])
当数组中所有 Promise 实例都成功时,它返回一个包含成功结果的数组,顺序与请求顺序相同。如果任何一个 Promise 失败,则进入失败回调。
const p1 = new Promise((resolve) => {
resolve(1);
});
const p2 = new Promise((resolve) => {
resolve(1);
});
const p3 = Promise.resolve('ok');
// 如果所有 Promise 都成功,result 将是一个包含 3 个结果的数组。
const result = Promise.all([p1, p2, p3]);
// 如果其中一个失败,result 将是失败 Promise 的值。
2. Promise.allSettled([])
执行不会失败;它返回一个数组,该数组对应于输入数组中每个 Promise 实例的状态。
const p1 = Promise.resolve(1);
const p2 = Promise.reject(-1);
Promise.allSettled([p1, p2]).then(res => {
console.log(res);
});
// 输出:
/*
[
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: -1 }
]
*/
3. Promise.any([])
如果输入数组中的任何一个 Promise 完成,则返回的实例将变为已完成状态,并返回第一个已完成 Promise 的值。如果所有 Promise 都被拒绝,则返回的实例将变为已拒绝状态。
const p1 = new Promise((resolve, reject) => {
reject(1);
});
const p2 = new Promise((resolve, reject) => {
reject(2);
});
const p3 = Promise.resolve("ok");
Promise.any([p1, p2, p3]).then(
(r) => console.log(r), // 输出 'ok'
(e) => console.log(e)
);
4. Promise.race([])
只要数组中的任何一个 Promise 状态发生变化,race
方法的状态也会相应地发生变化;第一个发生变化的 Promise 的值将传递给 race
方法的回调函数。
const p1 = new Promise((resolve) => {
setTimeout(() => {
resolve(10);
}, 3000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
throw new Error("I encountered an error");
}, 2000);
});
Promise.race([p1, p2]).then(
(v) => console.log(v), // 输出 10
(e) => console.log(e)
);
抛出异常不会改变 race
的状态;它仍然由 p1
决定。
高级用法
以下列出了9种高级用法,可以帮助开发者更高效、更优雅地处理异步操作。 每个用法都配有代码示例和详细解释。
1. 并发控制
使用 Promise.all
可以并行执行多个 Promise,但为了控制同时请求的数量,可以实现一个并发控制函数。 例如,限制同时进行的网络请求数量,避免服务器过载。
const concurrentPromises = async (promises, limit) => {
const results = [];
let running = 0;
const next = () => {
if (promises.length === 0) return;
const p = promises.shift();
running++;
p().then(res => {
results.push(res);
running--;
next();
}).catch(err => {
console.error("Error in concurrent promise:", err);
running--;
next();
});
};
for (let i = 0; i < limit; i++) {
next();
}
return new Promise(resolve => {
const check = () => {
if (running === 0 && promises.length === 0) {
resolve(results);
} else {
setTimeout(check, 10); //检查是否所有Promise都执行完成
}
};
check();
});
};
const promises = [
() => new Promise(resolve => setTimeout(() => resolve('promise1'), 1000)),
() => new Promise(resolve => setTimeout(() => resolve('promise2'), 2000)),
() => new Promise(resolve => setTimeout(() => resolve('promise3'), 500)),
() => new Promise(resolve => setTimeout(() => resolve('promise4'), 1500)),
() => new Promise(resolve => setTimeout(() => resolve('promise5'), 800))
];
concurrentPromises(promises, 2).then(results => console.log(results));
//输出结果顺序可能因并发执行而改变
2. Promise 超时
有时,您可能希望 Promise 在一定时间内未完成则自动拒绝。 这可以用于防止长时间等待无响应的请求。
const promiseWithTimeout = (promise, ms) =>
Promise.race([
promise,
new Promise((resolve, reject) => setTimeout(() => reject(new Error('Timeout after ' + ms + 'ms')), ms))
]);
const myPromise = new Promise(resolve => setTimeout(() => resolve('success'), 3000));
promiseWithTimeout(myPromise, 2000)
.then(result => console.log(result)) // 可能输出 'success',也可能超时
.catch(error => console.error(error)); // 可能输出超时错误
3. 取消 Promise
原生 JavaScript Promise 无法取消,但可以通过引入可控中断逻辑来模拟取消。 这对于需要在中途停止操作的情况非常有用。
let isCanceled = false;
const cancellablePromise = (promise) => {
return new Promise((resolve, reject) => {
promise.then(value => {
if (isCanceled) {
reject({ isCanceled: true, value });
} else {
resolve(value);
}
}).catch(error => {
if (isCanceled) {
reject({ isCanceled: true, error });
} else {
reject(error);
}
});
})
};
//测试案例
const p = cancellablePromise(new Promise(res => setTimeout(() => res('OK'), 3000)));
setTimeout(() => isCanceled = true, 1000);
p.then(console.log).catch(console.error) // 输出错误信息和isCanceled
4. 顺序执行 Promise 数组
有时您需要按顺序执行一系列 Promise,确保先前的异步操作完成之后才能开始下一个操作。 这对于依赖关系明确的任务非常重要。
const sequencePromises = async (promises) => {
let result = null;
for (const promise of promises) {
result = await promise();
}
return result;
};
const promises = [
() => new Promise(resolve => setTimeout(() => resolve('promise1'), 1000)),
() => new Promise(resolve => setTimeout(() => resolve('promise2'), 500)),
() => new Promise(resolve => setTimeout(() => resolve('promise3'), 2000))
];
sequencePromises(promises).then(result => console.log(result)); // 输出 promise3
5. Promise 的重试逻辑
当 Promise 因临时错误而被拒绝时,您可能希望重试其执行。 这对于网络请求等易受干扰的操作非常有用。
const retryPromise = async (promiseFn, maxAttempts, interval) => {
let attempts = 0;
while (attempts < maxAttempts) {
try {
const result = await promiseFn();
return result;
} catch (error) {
console.error(`Attempt ${attempts + 1} failed:`, error);
attempts++;
await new Promise(resolve => setTimeout(resolve, interval));
}
}
throw new Error('Max attempts reached');
};
const unreliablePromise = () => new Promise((resolve, reject) => {
if (Math.random() < 0.5) {
resolve('Success!');
} else {
reject(new Error('Failed!'));
}
});
retryPromise(unreliablePromise, 3, 1000)
.then(result => console.log(result))
.catch(error => console.error(error));
6. 确保 Promise 只解析一次
在某些情况下,您可能希望确保 Promise 只解析一次,即使 resolve
被多次调用。 这可以防止意外的重复操作。
const onceResolvedPromise = (executor) => {
let resolved = false;
return new Promise((resolve, reject) => {
executor(value => {
if (!resolved) {
resolved = true;
resolve(value);
}
}, reject);
});
};
const myPromise = onceResolvedPromise((resolve) => {
resolve('Success!');
resolve('Another Success!'); //这个resolve不会被执行
});
myPromise.then(result => console.log(result)); // 输出 Success!
7. 使用 Promise 代替回调函数
Promise 提供了一种更标准化、更方便的方式来处理异步操作,取代回调函数,使代码更易读和维护。
const callbackToPromise = (fn, ...args) => {
return new Promise((resolve, reject) => {
fn(...args, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
};
// 模拟一个使用回调函数的异步操作
const asyncOperation = (callback) => {
setTimeout(() => {
const result = Math.random() > 0.5 ? 'Success' : 'Failure';
callback(result === 'Failure' ? new Error('Operation failed') : null, result);
}, 1000);
};
callbackToPromise(asyncOperation)
.then(result => console.log('Success:', result))
.catch(error => console.error('Error:', error));
8. 动态生成 Promise 链
在某些情况下,您可能需要根据不同的条件动态创建一系列 Promise 链。 这对于需要根据运行时数据决定执行流程的场景非常有用。
const tasks = [
() => new Promise(resolve => setTimeout(() => resolve('Task 1'), 1000)),
() => new Promise(resolve => setTimeout(() => resolve('Task 2'), 500)),
() => new Promise(resolve => setTimeout(() => resolve('Task 3'), 2000))
];
const dynamicPromiseChain = async (tasks, condition) => {
let result = null;
for(const task of tasks){
if(!condition){
break;
}
result = await task();
}
return result;
};
dynamicPromiseChain(tasks, true)
.then(result => console.log('Result:', result))
.catch(error => console.error('Error:', error));
dynamicPromiseChain(tasks, false)
.then(result => console.log('Result:', result))
.catch(error => console.error('Error:', error));
9. 使用 Promise 实现简单的异步锁
在多线程环境中,可以使用 Promise 来实现简单的异步锁,确保只有一个任务可以同时访问共享资源。
let lock = Promise.resolve();
const acquireLock = async () => {
await lock;
return new Promise(resolve => {
lock = new Promise(r => setTimeout(() => {r(); lock = Promise.resolve(); resolve();},1000));
});
};
const accessResource = async () => {
await acquireLock();
console.log('Accessing resource...');
await new Promise(resolve => setTimeout(resolve, 2000)); // 模拟访问资源
console.log('Finished accessing resource.');
};
const runTask = async () => {
await accessResource();
}
const run = async ()=>{
await runTask();
await runTask();
}
run();
这些示例展示了 Promise 高级用法的具体实现,可以帮助开发者更好地理解和应用这些技巧。 请注意,实际应用中可能需要根据具体情况进行调整。
结论
Promise 是现代 JavaScript 异步编程中不可或缺的一部分。掌握其高级技巧将极大地提高开发效率和代码质量。通过上述各种方法,开发者可以更自信地处理复杂的异步场景,编写更易读、更优雅、更健壮的代码。