异步编程
promise
promise 的状态
ECMAScript 6 新增了Promise(期约)引用类型来实现异步编程机制,通过new操作符来实例化。它是一个由状态的对象,可能处于如下三种状态之一:
- 待定 pending:期约的初始状态,表示期约封装的异步操作尚未开始或正在执行中;
- 兑现 fulfilled(或解决 resolved):表示异步操作已完成,会返回一个解决值value;
- 拒绝 rejected:表示异步操作没有成功,会返回一个拒绝理由reason;
期约的状态是私有的,只能在内部进行改变,因此使用new操作符创建期约时要传入执行器函数,来初始化期约的异步行为和控制状态的最终转换。其中,状态转换是通过调用Promise.resolve()和Promise.reject()实现的。
Promise.prototype.then
①为期约实例添加处理程序,接收最多两个参数:onResolved处理程序和onRejected处理程序,在期约分别进入“解决”和“拒绝”状态时执行,解决值value会作为onResolved处理程序的参数,拒绝原因reason会作为onRejected处理程序的参数。这两个处理程序都是在触发时加入到消息队列,并非立即执行,满足“非重入”特性。注意,传给then()任何非函数类型的参数都会被静默忽略。
②该方法也会返回一个新的期约实例,这个新期约实例基于处理程序的返回值构建,返回值通过Promise.resolve()或Promise.reject()包装生成新期约,也即返回值充当解决值或拒绝原因。
- 如果处理程序中没有显式的返回语句(,则Promise.resolve()包装默认的返回值undefined。
- 如果处理程序抛出异常,返回的是拒绝的期约;但处理程序中返回错误值不会导致返回一个拒绝的期约,而是将这个错误值作为一个解决值包装在一个解决的期约中并返回。
let p1 =Promise.resolve(1); // let p1 = Promise.reject('rejected'); let p2 = p1.then(); console.log(p2); //[[PromiseState]]: "fulfilled" [[PromiseResult]]: 1 let p3 = p1.then(()=>{throw 'error'}); console.log(p3);//[[PromiseState]]: "rejected" [[PromiseResult]]: "error" let p4 = p1.then(()=>Error('e')); console.log(p4);//[[PromiseState]]: "fulfilled" [[PromiseResult]]: Error: e
关于异步错误的注意事项:
上述代码抛出错误的时间可能与我们想象中的不一样:
可以看到错误是在最后才抛出的,因为期约中抛出的错误是从消息队列中异步抛出的,并不影响正在运行的同步指令。而下面代码进行同步抛出,则会停止执行错误所在语句的下一条语句:
throw Error('error'); console.log("continue");//该语句不会执行
因此,期约的异步错误只能通过异步的onRejected处理程序捕获,不能用try/catch结构进行捕获。但它们出发点都是相同的,都是为了捕获错误之后将其隔离,不影响正常逻辑的进行。因此,onRejected处理程序的任务应该是在捕获异步错误之后返回一个解决的期约。
Promise.prototype.catch()
该方法用于给期约添加拒绝处理程序,相当于调用Promise.prototype.then(null,onRejected),在返回新期约实例方面与then()的onRejected情况是一样的;
Promise.prototype.finally()
该方法用于给期约添加onFinally处理程序,也即无论期约转换为fulfilled还是reject,该程序都会执行,用来减少onResolved和onRejected中出现的重复代码,减少冗余。注意,在返回新期约实例方面,由于该方法无法判断期约的状态,因此它大多数情况下表现为父期约的传递。
Promise.all()
该静态方法用于合成期约,它创建的期约对象只有当传入的多个期约全部解决之后才会去解决。若包含的期约中至少有一个待定或拒绝,那合成的期约也会待定或拒绝。
手动实现Promise.all()
首先,了解Promise.all()的基本功能:
- 如果传入的可迭代对象为空,Promise.all会同步返回一个已完成状态的期约;
- 如果所有传入的promise都变成完成状态,或者传入的可迭代对象内没有promise,Promise.all异步返回已完成状态期约;
- 返回的promise包含所有的传入迭代参数对象的值,包括非Promise值;
- 如果传入的promise中有一个失败,Promise.all异步地将失败的那个结果给失败状态的回调函数,而不管其它promise是否完成;
function myAll(promises){ let arr = []; //第三点,存储所有传入的值,当所有promise完成后将这个数组作为一个期约值返回 let count = 0; //计算已解决的promise个数 if(promises.length===0){ return Promise.resolve([]); } return new Promise((resolve ,reject)=> { for(let i=0;i<promises.length;i++) { console.log('当前遍历到promise['+i+']:'+promises[i]); //promise是异步的,因此then中的回调函数是要等promise解决后才调用 Promise.resolve(promises[i]).then(res => { //外部封装一层Promise.resolve(),是为了将数组中的非期约值包装为一个期约 console.log('promises[' + i + ']已完成'); arr[i] = res; //按传入参数的顺序存储 count++; if(count===promises.length){//第二点,当所有期约都完成后,才将这个数组作为一个已完成的期约的值,此时,对Promise.all进行then捕获 console.log('所有promise均已完成') resolve(arr); } },reason=>{ reject(reason) }) } }) } function f1(){ let p1 = new Promise((resolve,reject) => { setTimeout(reject,1000,'f1') //1秒后在将promise更新为拒绝状态 }) // console.log(p1) //可以看到输出的p1状态是<pending>,也即函数的返回值是一个未解决的promise return p1; } function f3(){ return new Promise(resolve => { setTimeout(resolve,2000,'f3') }) } const p = [f1(),2,f3()]; myAll(p).then((value)=>{ console.log(value) }).catch(e=>console.log(e+'拒绝'));
控制台输出: //可以看到,循环中先执行同步代码,每个遍历到的promise后面的then操作都是异步的 当前遍历到promise[0]:[object Promise] 当前遍历到promise[1]:2 当前遍历到promise[2]:[object Promise] promises[1]已完成 f1拒绝 //遇到拒绝的promise时,立即返回失败行为,由promise.all().catch来捕获进行输出 promises[2]已完成 //f3的定时为2秒,最后才完成
Promise.race()
该静态方法返回一个包装期约,是一组期约中最先解决或拒绝的期约的镜像。
手动实现Promise.race()
function myRace(promises){ return new Promise((resolve, reject) => { for(let i=0;i<promises.length;i++){ console.log('当前遍历到promise['+i+']:'+promises[i]); Promise.resolve(promises[i]).then(resolve,reject) } }) } myRace(p).then(value => console.log(value+'先完成')).catch(reason => console.log(reason+'先拒绝'))
控制台输出: 当前遍历到promise[0]:[object Promise] 当前遍历到promise[1]:2 当前遍历到promise[2]:[object Promise] 2先完成 //一旦有一个promise先完成或拒绝,就直接返回
异步函数async/await
ECMAScript 8新增了两个关键字async/await,来解决利用异步结构组织代码的问题——前面可以发现,异步操作代码都要塞到promise的处理程序中。
- async关键字
用于声明异步函数,使函数具有异步特性,但在参数和闭包方面与普通函数无异。异步函数会将返回值用Promise.resolve()包装成一个promise对象(若没有return也会返回undefined),该对象由then()方法“解包”。若在异步函数中抛出错误则返回一个拒绝的promise。
async function baz(){ new Promise(((resolve, reject) => setTimeout(resolve,1000,2))).then((x)=>console.log(x)); //1秒后会将期约状态转换为resolve,此时调用then()中的onResolved处理处理程序输出解决值2 console.log('1'); return 3; } baz().then((x)=>setTimeout(console.log,2000,x));//异步函数返回一个解决了的期约(解决值为3),onResolved处理程序会在2秒后输出它 console.log('current');
输出顺序为:
- await关键字
必须在异步函数中使用,用于暂停执行异步函数后面的代码,让出JavaScript运行时的执行线程。一旦await关键字“解包”了对象,则恢复异步函数中的操作。若不是一个期约对象,则这个值被当作是解决了的期约。对拒绝的期约使用await关键字会释放错误值,将拒绝期约返回。
async function baz(){ await new Promise(((resolve, reject) => setTimeout(resolve,1000,2))).then((x)=>console.log(x)); console.log('1'); //会一直等到await关键字将对象“解包”后才执行 return 3; } baz().then((x)=>setTimeout(console.log,2000,x)); console.log('current');
输出顺序:
示例:
async function foo(){ console.log(2); await new Promise((resolve, reject)=>setTimeout(resolve,0,8)).then((x)=>{console.log(x)}); console.log(9); } async function bar(){ console.log(4); console.log(await 6); console.log(7); } console.log(1); foo(); console.log(3); bar(); console.log(5);
输出顺序:1,2,3,4,5,6,7,8,9
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)