异步编程

 


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

posted @   ˙鲨鱼辣椒ゝ  阅读(34)  评论(0编辑  收藏  举报
编辑推荐:
· 从 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)
点击右上角即可分享
微信分享提示