深入理解ES6 Promise
一、为什么需要 Promise?
在 JavaScript 中,异步操作(如网络请求、文件读取、定时器)通常通过回调函数处理。但多个异步操作依赖或串行时,会出现回调地狱:
// 回调地狱示例setTimeout(()=>{console.log('第一步');setTimeout(()=>{console.log('第二步');setTimeout(()=>{console.log('第三步');},1000);},1000);},1000);回调地狱的缺点:
- 代码横向增长,可读性差
- 难以捕获错误
- 难以并行或竞速处理多个异步任务
Promise 正是为了解决这些问题而诞生的,它让异步代码更接近同步写法的风格,且提供了统一的错误处理机制。
二、Promise 是什么?
Promise 是 ES6 新增的一个内置构造函数,代表一个异步操作的最终完成(或失败)及其结果值。
字面理解:Promise 是一个“承诺”,承诺在未来某个时刻会给你一个结果(成功或失败)。
一个 Promise 对象处于以下三种状态之一:
| 状态 | 含义 | 特点 |
|---|---|---|
pending | 初始状态,未完成也未拒绝 | 可以转变为fulfilled或rejected |
fulfilled | 操作成功完成 | 有一个value(结果值) |
rejected | 操作失败 | 有一个reason(失败原因) |
状态一旦改变,就永久固定,不会再变。
三、Promise 的基本用法
1. 创建一个 Promise
使用new Promise(executor),其中executor是一个立即执行的函数,它接收两个参数:resolve和reject。
constpromise=newPromise((resolve,reject)=>{// 异步操作constsuccess=true;setTimeout(()=>{if(success){resolve('成功的数据');// 将状态改为 fulfilled}else{reject('失败的原因');// 将状态改为 rejected}},1000);});2. 使用.then()和.catch()消费 Promise
promise.then((value)=>{console.log('成功:',value);}).catch((error)=>{console.error('失败:',error);});.then()接收两个可选参数:onFulfilled和onRejected。通常只传第一个,错误交给.catch()。.catch()是.then(null, onRejected)的语法糖。
3..finally()—— 无论成功失败都会执行
promise.then(value=>console.log(value)).catch(error=>console.error(error)).finally(()=>console.log('操作结束,清理资源'));四、Promise 的链式调用(核心特性)
.then()或.catch()返回一个新的 Promise,因此可以串联多个异步操作。
newPromise((resolve)=>{resolve(1);}).then((value)=>{console.log(value);// 1returnvalue+1;}).then((value)=>{console.log(value);// 2returnvalue+1;}).then((value)=>{console.log(value);// 3});链式调用中的返回值处理规则:
- 如果回调函数返回一个普通值(包括
undefined),它会自动被包装成一个fulfilled的 Promise。 - 如果返回一个 Promise,那么下一个
.then()会等待这个 Promise 的结果。 - 如果抛出异常(
throw new Error()),会进入下一个.catch()。
示例:模拟异步任务顺序执行
functionasyncTask(name,duration){returnnewPromise((resolve)=>{setTimeout(()=>{console.log(`${name}完成`);resolve(name);},duration);});}asyncTask('任务1',1000).then(()=>asyncTask('任务2',500)).then(()=>asyncTask('任务3',800)).then(()=>console.log('所有任务完成'));五、Promise 的静态方法
1.Promise.resolve(value)
快速返回一个状态为fulfilled的 Promise。
Promise.resolve('直接成功').then(console.log);// 直接成功如果value本身是一个 Promise,则直接返回该 Promise。
2.Promise.reject(reason)
快速返回一个状态为rejected的 Promise。
Promise.reject('出错了').catch(console.error);// 出错了3.Promise.all(iterable)
并行执行多个 Promise,所有都成功才成功,结果按顺序组成数组;任何一个失败则立即失败,并返回第一个失败的原因。
constp1=Promise.resolve(1);constp2=Promise.resolve(2);constp3=newPromise((resolve)=>setTimeout(resolve,1000,3));Promise.all([p1,p2,p3]).then((results)=>{console.log(results);// [1, 2, 3] (等待 1 秒后输出)});失败示例:
constp1=Promise.resolve(1);constp2=Promise.reject('错误');Promise.all([p1,p2]).catch((err)=>{console.log(err);// '错误'});4.Promise.race(iterable)
竞速:返回最先 settled(成功或失败)的那个 Promise 的结果。
constp1=newPromise((resolve)=>setTimeout(resolve,500,'慢'));constp2=newPromise((resolve)=>setTimeout(resolve,100,'快'));Promise.race([p1,p2]).then(console.log);// '快'常用于设置超时:
functionfetchWithTimeout(url,timeout=5000){constfetchPromise=fetch(url);consttimeoutPromise=newPromise((_,reject)=>setTimeout(()=>reject(newError('请求超时')),timeout));returnPromise.race([fetchPromise,timeoutPromise]);}5.Promise.allSettled(iterable)(ES2020)
等待所有 Promise 都 settle(成功或失败),返回每个对象的状态和结果(或原因)。永远不会进入.catch()。
constpromises=[Promise.resolve('成功'),Promise.reject('失败'),Promise.resolve('另一个成功'),];Promise.allSettled(promises).then((results)=>{console.log(results);/* [ { status: 'fulfilled', value: '成功' }, { status: 'rejected', reason: '失败' }, { status: 'fulfilled', value: '另一个成功' } ] */});6.Promise.any(iterable)(ES2021)
只要有一个 Promise 成功就返回那个成功的结果;如果所有都失败,则返回一个AggregateError(聚合错误)。
constp1=Promise.reject('错误1');constp2=Promise.reject('错误2');constp3=Promise.resolve('成功');Promise.any([p1,p2,p3]).then(console.log);// '成功'全部失败的情况:
Promise.any([Promise.reject('a'),Promise.reject('b')]).catch((err)=>{console.log(errinstanceofAggregateError);// trueconsole.log(err.errors);// ['a', 'b']});六、错误处理的最佳实践
总是添加
.catch()或者在每个.then()的第二个参数中处理错误,否则错误会被静默吞没(虽然浏览器会报 unhandled rejection,但不推荐依赖于此)。在链式调用的末尾统一捕获错误:
doSomething().then(doSomethingElse).then(doThirdThing).catch(handleError);// 捕获前面任意一步的错误不要在
.then()中抛出普通字符串,而应该抛出Error对象,以便获得堆栈信息。async/await与 try-catch是 Promise 的语法糖,但底层依然是 Promise,理解 Promise 是掌握async/await的基础。
七、Promise 与回调地狱对比
回调地狱:
getUser((user)=>{getPosts(user.id,(posts)=>{getComments(posts[0].id,(comments)=>{console.log(comments);});});});Promise 版本(假设每个函数都返回 Promise):
getUser().then(user=>getPosts(user.id)).then(posts=>getComments(posts[0].id)).then(comments=>console.log(comments)).catch(error=>console.error(error));扁平化,更易读。
八、进阶示例:串行与并行控制
1. 串行执行一组异步任务(逐个进行)
consttasks=[task1,task2,task3];// 每个 task 返回 Promisetasks.reduce((prevPromise,task)=>{returnprevPromise.then(()=>task());},Promise.resolve());2. 限制并发数量(例如同时最多 3 个请求)
asyncfunctionlimitConcurrent(tasks,limit){constresults=[];constexecuting=[];for(consttaskoftasks){constp=task().then(r=>results.push(r));executing.push(p);if(executing.length>=limit){awaitPromise.race(executing);// 清除已完成的任务executing.splice(executing.findIndex(e=>e===p),1);}}awaitPromise.all(executing);returnresults;}九、Promise 的局限性
| 问题 | 说明 |
|---|---|
| 不可取消 | 一旦创建 Promise,无法从外部取消它的执行(某些库提供 cancelable promise,但非标准)。 |
| 无法获取中间状态 | 无法同步获取“进度”,只能拿到最终结果(不过可以自己实现进度通知)。 |
| 错误可能被吞没 | 若没有.catch()且未被async/await捕获,错误将静默消失(现代浏览器会打印 unhandledrejection 警告)。 |
| 新手容易误解 | 例如忘记return导致链断裂,或在.then()中创建新的 Promise 但未返回。 |
十、总结
- Promise 是异步编程的基石,它将回调函数的嵌套转化为链式调用。
- 核心掌握:状态(pending/fulfilled/rejected)、链式调用、静态方法(
all、race、allSettled、any)。 - 搭配
async/await使用时,Promise 依然在幕后工作,理解 Promise 有助于写出更可靠的异步代码。 - 实际开发中,几乎所有现代 JavaScript API(如
fetch、fs.promises、setTimeout包装)都基于 Promise。
