当前位置: 首页 > Web前端 > JavaScript

Promise详解

时间:2023-03-27 15:19:19 JavaScript

前言:在日常开发中,几乎每天都能看到异步操作的身影。传统的解决方案是使用回调函数。随着程序逻辑越来越复杂,回调函数的方式越来越繁琐,很容易出现回调地狱,于是出现了一个更合理、更强大的替代方案——Promise,接下来,学习Promise是如何解决异步操作的.1.基本定义:Promise对象用于表示一个异步操作的最终完成(或失败),及其结果值。constpromise1=newPromise((resolve,reject)=>{setTimeout(()=>{resolve('foo');},300);});承诺1.then((value)=>{console.log(value);//预期输出:"foo"});安慰。log(promise1);//预期输出:[objectPromise]运行上面的代码,先打印[objectPromise],300ms后打印foo语法newPromise(function(resolve,reject){...}/*executor*/)参数executorexecutor是一个函数,有resolve和reject两个参数。当Promise构造函数执行时,立即调用executor函数,并将resolve和reject函数作为参数传递给executor(在Promise构造函数返回promise实例对象之前调用executor函数)。当resolve和reject函数被调用时,它们分别将promise的状态更改为fulfilled(完成)或rejected(失败)。执行器通常在内部执行一些异步操作。一旦执行了异步操作(可能成功/失败),要么调用resolve函数将promise状态更改为fulfilled,要么调用reject函数将promise状态更改为rejected。如果在执行函数中抛出错误,则承诺的状态为拒绝。执行函数的返回值被忽略。这个描述的细分是:在实例化一个Promise对象的时候,需要传入一个executor函数,所有的业务代码都需要写在这个函数中;executor函数会在constructor执行时被调用,实例化对象还没有Create,resolve和reject函数作为参数传递给executor,当resolve和reject函数被调用时,promise的状态分别改为fulfilled(完成)或rejected(失败)。状态一旦改变,就不会再改变,随时都可以得到这个结果。如果执行器中的代码抛出错误,则promise状态为rejected;执行函数的返回值被忽略。状态Promise有以下几种状态:pending:初始状态,既不是成功状态也不是失败状态。fulfilled:表示操作成功完成。rejected:表示操作失败。处于挂起状态的Promise对象可能会变成fulfilled并向相应的状态处理方法传递一个值,或者它可能会变成rejected并传递失败信息。当其中任何一种情况发生时,都会调用Promise对象的then方法绑定的处理方法(handlers)(then方法包含两个参数:onfulfilled和onrejected,都是Function类型。当Promise状态为fulfilled时,调用then的onfulfilled方法,当Promise状态为rejected时,调用then的onrejected方法,这样异步操作的完成和绑定的处理方法之间不存在竞争)。constpromise1=newPromise((resolve,reject)=>{setTimeout(()=>{resolve('fulfilled');},1000);});constpromise2=newPromise((resolve,reject)=>{setTimeout(()=>{reject('rejected');},3000);});promise1.then((fulfilled)=>{console.log(fulfilled);},(rejected)=>{console.log(rejected);});promise2.then((fulfilled)=>{console.log(fulfilled);},(rejected)=>{console.log(拒绝了);});运行上面的代码,1秒后打印完成,3秒后打印被拒绝??。被拒绝的Promise也可以被.catch捕获,因为Promise.prototype.then和Promise.prototype.catch方法返回promise对象,所以它们可以被链接起来。所以上面的代码可以改成:promise1.then((fulfilled)=>{console.log(fulfilled);}).catch((rejected)=>{console.log(rejected);});promise2.then((fulfilled)=>{console.log(fulfilled);}).catch((rejected)=>{console.log(rejected);});二。深入理解1.Promise是用来管理异步编程的,本身不是异步的,当有新的Promise发出时,executor函数会立即执行,但是我们一般都是在executor函数中处理一个异步操作。例如下面这段代码:letfirstPromise=newPromise(()=>{setTimeout(()=>{console.log(1)},1000)console.log(2)})console.log(3)//2312.Promise采用回调函数延迟绑定技术。在执行resolve函数的时候,回调函数还没有绑定,所以只能推迟回调函数的执行。这到底是什么意思?我们先看下面的例子:letp1=newPromise((resolve,reject)=>{console.log(1);resolve('扬帆起航')console.log(2)})//then:Set成功或失败后的处理方法p1.then(result=>{//p1延迟绑定回调函数console.log('success'+result)},reason=>{console.log('failure'+reason)})console.log(3)//1//2//3//浪中航行成功时,新的Promise先执行executor函数,打印1和2,当Promise执行resolve时,触发microtask,或继续执行同步任务。在执行p1.then的时候,存储两个函数(这两个函数还没有被执行),然后打印出3。此时同步任务执行完毕,最后执行刚才的microtask,这样Execute.then中的成功方法。3、错误处理,多个Promise链操作的错误捕获可以通过一个catch来处理;例如,下面这段代码:(executor)p1.then(resualt=>{console.log(1)returnnewPromise(executor)}).then(resualt=>{console.log(2)returnnewPromise(executor)}).then(resualt=>{console.log(3)returnnewPromise(executor)}).catch((error)=>{console.log('error',error)})这段代码中有四个Promise对象。无论哪个对象抛出异常,最后一个.catch都可以用来捕获异常。这样就可以将所有Promise对象的errors合并成一个函数来处理。这样就解决了每个任务需要单独处理异常的问题。4.常用方法Promise.resolve()Promise.resolve(value)方法返回一个用给定值解析的Promise对象。Promise.resolve()等价于:Promise.resolve('foo')//等价于newPromise(resolve=>resolve('foo'))Promise.resolve方法的参数分为四种情况。(1)参数是一个Promise实例constp1=newPromise(function(resolve,reject){setTimeout(()=>reject(newError('fail')),3000)})constp2=newPromise(function(resolve,reject){setTimeout(()=>resolve(p1),1000)})p2.then(result=>console.log(result)).catch(error=>console.log(error))//错误:失败在上面的代码中,p1是一个Promise,它在3秒后被拒绝。1秒后p2的状态发生变化,resolve方法返回p1。由于p2返回了另一个Promise,所以p2的状态是无效的,p2的状态由p1的状态决定。所以,后面的then语句都是针对后者(p1)的。又过了2秒,p1变成rejected,触发catch方法指定的回调函数。(2)参数不是有then方法的对象,或者根本不是对象。Promise.resolve("Success").then(function(value){//Promise.resolve方法的参数会同时传递给回调函数console.log(value);//"Success"},function(value){//不会被调用});(3)不带参数Promise.resolve()方法允许不带参数调用,直接返回一个解析状态的Promise对象。如果想获取一个Promise对象,更方便的方式是直接调用Promise.resolve()方法。Promise.resolve().then(function(){console.log('two');});console.log('一个');//一二(4)参数是一个thenable对象thenable对象指的是对于一个有then方法的对象,Promise.resolve方法会将该对象转为Promise对象,然后立即执行该thenable对象的then方法。让thenable={那么:函数(解决,拒绝){解决(42);}};letp1=Promise.resolve(thenable);p1.then(function(value){console.log(value);//42});Promise.reject()Promise.reject()方法返回带有拒绝原因的Promise。newPromise((resolve,reject)=>{reject(newError("Error"));});//等同于Promise.reject(newError("Error"));//使用方法Promise.reject(newError("BOOM!")).catch(error=>{console.error(error);});值得注意的是,在调用resolve或reject之后,Promise的使命就完成了,后续的操作应该放在then方法中,而不是直接在resolve或reject之后。因此,最好在它们前面加上return语句,这样就不会出现意外。newPromise((resolve,reject)=>{returnreject(1);//后面的语句不会执行console.log(2);})Promise.all()varp1=Promise.resolve(1)varp2=Promise.resolve({a:2})varp3=newPromise(function(resolve,reject){setTimeout(function(){resolve(3)},3000)})Promise.all([p1,p2,p3]).then(result=>{//返回的结果按照实例在Array中写入的顺序排列。console.log(result)})Promise.all生成并返回一个新的Promise对象,所以可以使用Promise实例的所有方法。当传入的参数promise数组中的所有Promise对象都被resolved时,这个方法就会返回,新创建的Promise会使用这些promise的值。如果参数中的任何一个promise被拒绝,整个Promise.all调用将立即终止,并返回一个新的拒绝的Promise对象。Promise.allSettled()有时候,我们并不关心异步操作的结果,只关心这些操作是否完成。这时候ES2020引入的Promise.allSettled()方法就非常有用了。如果没有这种方法,要保证所有的操作都完成是很麻烦的。Promise.all()方法无法做到这一点。如果有这样一种场景:一个页面有三个区域,对应三个独立的界面数据,使用Promise。三个区域的数据都无法找回。显然,这种情况是我们不能接受的。Promise.allSettled的出现可以解决这个痛点:Promise.allSettled([Promise.reject({code:500,msg:'Serviceexception'}),Promise.resolve({code:200,list:[]}),Promise.resolve({code:200,list:[]})]).then(res=>{console.log(res)/*0:{status:"rejected",原因:{...}}1:{status:"fulfilled",value:{...}}2:{status:"fulfilled",value:{...}}*///过滤掉rejected状态,尽可能保证页面区域数据渲染RenderContent(res.filter(el=>{returnel.status!=='rejected'}))})Promise.allSettled和Promise.all类似,它的参数接受一个Promises数组,返回一个新的Promise,唯一的区别就是不会短路,也就是说,当所有的Promise都处理完了,我们就可以拿到每一个Promise的状态,不管处理成功与否。Promise.race()Promise.all()方法的效果是“谁跑得慢,谁执行回调”,那么还有一个方法“谁跑得快,谁执行回调”,这就是Promise.race()方法,这个词原本是种族的意思。race的用法和all一样,接收一个promise对象数组作为参数。Promise.all会在所有接收到的对象promises变为FulFilled或Rejected后继续处理。相反,只要promise对象进入FulFilled或Rejected状态,Promise.race就会继续。用于后续处理。//在`delay`毫秒后执行resolvefunctiontimerPromisefy(delay){returnnewPromise(resolve=>{setTimeout(()=>{resolve(delay);},delay);});}//任何promise变成Ifresolve或拒绝,程序将停止运行Promise.race([timerPromisefy(1),timerPromisefy(32),timerPromisefy(64)]).then(function(value){console.log(value);//=>1});上面代码创建了3个promise对象,这3个promise对象分别在1ms、32ms和64ms后确定,即FulFilled,第一个确定1ms后,调用.then函数注册的回调。Promise.prototype.finally()ES9添加了finally()方法来返回一个Promise。当promise结束时,无论结果是fulfilled还是rejected,都会执行指定的回调函数。这为无论Promise是否成功实现都需要执行的代码提供了一种方式。这避免了需要在then()和catch()中分别编写一次相同的语句。比如我们发送请求之前会有一个loading。我们发送请求后,不管请求有没有错误,我们都想关闭加载。this.loading=truerequest().then((res)=>{//做某事}).catch(()=>{//记录错误}).finally(()=>{this.loading=false})finally方法的回调函数不接受任何参数,说明finally方法中的操作应该是独立于状态的,不依赖于Promise的执行结果。3、实际应用假设有这样一个需求:红灯3s亮一次,绿灯1s亮一次,黄灯2s亮一次;如何让三个灯交替反复亮起?三个灯函数已经存在:}这道题的复杂之处在于需要“交替重复”灯,而不是转一圈就结束,我们可以通过递归来实现://使用promise实现lettask=(timer,light)=>{returnnewPromise((resolve,reject)=>{setTimeout(()=>{if(light==='red'){red()}if(light==='green'){green()}if(light==='yellow'){yellow()}resolve()},timer);})}letstep=()=>{task(3000,'red').then(()=>task(1000,'green')).then(()=>task(2000,'yellow')).then(step)}step()也可以通过async/await实现://async/awaitimplementsletstep=async()=>{awaittask(3000,'red')awaittask(1000,'green')awaittask(2000,'yellow')step()}step()参考你真的了解Promise吗