当前位置: 首页 > 科技观察

初学者应该阅读JavaScriptPromise完全指南

时间:2023-03-18 18:25:19 科技观察

1.JavaScriptPromisesPromise是一个允许我们处理异步操作的对象,它是es5早期回调的替代方案。与回调相比,Promises有很多优势,例如使异步代码更易于阅读。提供组合错误处理。* 更好的过程控制,允许异步并行或串行执行。回调更容易出现深层嵌套结构(也称为回调地狱)。像这样:a(()=>{b(()=>{c(()=>{d(()=>{//andsoon...});});});});如果将这些功能转换为Promises,则可以将它们链接起来以生成更易于维护的代码。像这样:Promise.resolve().then(a).then(b).then(c).then(d).catch(console.error);在上面的示例中,Promise对象公开了.then和.catch方法,我们稍后将探讨这些方法。1.1如何将已有的回调API转为Promise?我们可以使用Promise构造函数将回调转换为Promise。Promise构造函数接受带有两个参数resolve和reject的回调。Resolve:是异步操作完成时应调用的回调。Reject:是发生错误时要调用的回调函数。构造函数立即返回一个对象,即Promise实例。在promise实例上使用.then方法时,您可以在Promise“完成”时收到通知。让我们看一个例子。承诺只是回调?并不真地。Promises不仅仅是回调,而且它们确实对.then和.catch方法使用异步回调。Promises是回调之上的抽象,我们可以链接多个异步操作并更优雅地处理错误。让我们看看它的实际效果。Promise反模式(PromisesHell)a(()=>{b(()=>{c(()=>{d(()=>{//andsoon...});});});});不要将上述回调转换为以下Promise形式:a().then(()=>{returnb().then(()=>{returnc().then(()=>{returnd().then(()=>{//??请永远不要这样做!??});});});});上面的转换也形成了Promise地狱,所以不要这样做。相反,最好执行以下操作:a().then(b).then(c).then(d)timeout您认为以下程序的输出是什么?constpromise=newPromise((resolve,reject)=>{setTimeout(()=>{resolve('timeisup?');},1e3);setTimeout(()=>{reject('Oops');},2e3);});promise.then(console.log).catch(console.error);输出:timeisup?糟糕!oroutput:timeisup?是后者,因为当一个Promise被resolved后,就不能再被reject了。一旦调用一个方法(resolve或reject),另一个方法就会失败,因为promise处于稳定状态。让我们探索一个承诺的所有不同状态。1.2Promise状态Promise可以分为四种状态:?Pending:初始状态,异步操作还在进行中。?Fulfilled:操作成功,调用.then回调,如.then(onSuccess)。??Rejected:操作失败,它调用.catch或.then(如果有的话)的第二个参数。例如.catch(onError)或.then(...,onError)。已解决:这是承诺的最终状态。承诺已死,没有其他方法可以解决或拒绝它。.finally方法被调用1.3Promise实例方法PromiseAPI公开了三个主要方法:then、catch和finally。让我们逐案讨论。Promisethen方法允许您在异步操作成功或失败时收到通知。它有两个参数,一个表示成功执行,一个表示错误。promise.then(onSuccess,onError);你也可以使用catch来处理错误:promise.then(onSuccess).catch(onError);Promise链接然后返回一个新的Promise,因此多个Promise可以链接在一起。像下面的例子:Promise.resolve().then(()=>console.log('then#1')).then(()=>console.log('then#2')).then(()=>console.log('then#3'));Promise.resolve立即将Promise视为成功。因此,将调用以下所有内容。输出将是then#1then#2then#3PromisecatchPromise。catch方法将一个函数作为参数来处理错误。如果没有出错,则永远不会调用catch方法。假设我们有以下承诺:1秒后解析或拒绝并打印出它们的字母。consta=()=>newPromise((resolve)=>setTimeout(()=>{console.log('a'),resolve()},1e3));constb=()=>newPromise((resolve)=>setTimeout(()=>{console.log('b'),resolve()},1e3));constc=()=>newPromise((resolve,reject)=>setTimeout(()=>{console.log('c'),reject('Oops!')},1e3));constd=()=>newPromise((resolve)=>setTimeout(()=>{console.log('d'),resolve()},1e3));请注意,c使用reject('Oops!')模拟拒绝。Promise.resolve().then(a).then(b).then(c).then(d).catch(console.error)输出如下:在这种情况下,您可以看到a、b和c错误消息.我们可以使用then函数的第二个参数来处理错误。但是,请注意,将不再执行catch。Promise.resolve().then(a).then(b).then(c).then(d,()=>console.log('cerroredoutbutnobigdeal')).catch(console.error)由于我们正在处理。then(...,onError)部分出错,因此未调用catch。d不会被调用。如果你想忽略错误并继续Promise链,你可以在c上添加一个catch。像这样:Promise.resolve().then(a).then(b).then(()=>c().catch(()=>console.log('errorignored'))).then(d).catch(console.error)当然,这种过早的捕获错误并不是很好,因为在调试过程中容易忽略一些潜在的问题。Promisefinallyfinally方法只会在Promise状态被解决时被调用。如果你希望一段代码即使发生错误也一直执行,可以在.catch之后使用.then。Promise.resolve().then(a).then(b).then(c).then(d).catch(console.error).then(()=>console.log('alwayscalled'));或者您可以使用.finally关键字:Promise.resolve().then(a).then(b).then(c).then(d).catch(console.error).finally(()=>console.log('总是被调用'));1.4Promise类方法我们可以直接使用Promise对象中的四个静态方法。Promise.allPromise.rejectPromise.resolvePromise.racePromise.resolve和Promise.reject这两个是允许Promise立即解决或拒绝的辅助函数。可以传递一个参数作为下一个.then的接收:Promise.resolve('Yay!!!').then(console.log).catch(console.error)将输出Yay!!!Promise.reject('Oops').then(console.log).catch(console.error)使用Promise.all并行执行多个Promise通常,Promise是一个接一个地顺序执行,但你也可以并行使用它们。假设是从两个不同的API轮询数据。如果它们不相关,我们可以使用Promise.all()同时触发两个请求。在此示例中,主要功能是将美元转换为欧元,我们有两个单独的API调用。一个用于BTC/USD,另一个用于获取EUR/USD。如您所料,这两个API调用可以并行进行。但是,我们需要一种方法来知道最终价格的计算何时同时完成。我们可以使用Promise.all,它通常在启动多个异步任务并发运行并为其结果创建承诺后使用,这样就可以等待所有任务完成。constaxios=require('axios');constbitcoinPromise=axios.get('https://api.coinpaprika.com/v1/coins/btc-bitcoin/markets');constdollarPromise=axios.get('https://api.exchangeratesapi.io/latest?base=USD');constcurrency='EUR';//GetthepriceofbitcoinsonPromise.all([bitcoinPromise,dollarPromise]).then(([bitcoinMarkets,dollarExchanges])=>{constbyCoinbaseBtc=d=>d.exchange_id==='coinbase-pro'&&d.pair==='BTC/USD';constcoinbaseBtc=bitcoinMarkets.data.find(byCoinbaseBtc)constcoinbaseBtcInUsd=coinbaseBtc.quotes.USD.price;currency];returnrate*coinbaseBtcInUsd;}).then(price=>console.log(`TheBitcoinin${currency}is${price.toLocaleString()}`)).catch(console.log)如您所见,Promise.all接受一组Promises。当两个请求的请求都完成后,我们就可以计算价格了。再举一个例子:consta=()=>newPromise((resolve)=>setTimeout(()=>resolve('a'),2000));constb=()=>newPromise((resolve)=>setTimeout(()=>resolve('b'),1000));constc=()=>newPromise((resolve)=>setTimeout(()=>resolve('c'),1000));constd=()=>newPromise((resolve)=>setTimeout(()=>resolve('d'),1000));console.time('promise.all');Promise.all([a(),b(),c(),d()]).then(results=>console.log(`Done!${results}`)).catch(console.error).finally(()=>console.timeEnd('promise.all'));解决这些承诺需要多长时间?5秒?1秒?还是2秒?这个留给你自己去验证。PromiseracePromise.race(iterable)方法返回一个承诺,一旦迭代器中的承诺之一解决或拒绝,该承诺就会解决或拒绝。consta=()=>newPromise((resolve)=>setTimeout(()=>resolve('a'),2000));constb=()=>newPromise((resolve)=>setTimeout(()=>resolve('b'),1000));constc=()=>newPromise((resolve)=>setTimeout(()=>resolve('c'),1000));constd=()=>newPromise((resolve)=>setTimeout(()=>resolve('d'),1000));console.time('promise.race');Promise.race([a(),b(),c(),d()]).then(results=>console.log(`Done!${results}`)).catch(console.error).finally(()=>console.timeEnd('promise.race'));输出它是什么输出b。使用Promise.race,第一个完成的将是最后一个返回结果。你可能会问:Promise.race的目的是什么?我不经常使用它。但是,在某些情况下它可以派上用场,例如定时请求或批处理请求数组。Promise.race([fetch('http://slowwly.robertomurray.co.uk/delay/3000/url/https://api.jsonbin.io/b/5d1fb4dd138da811182c69af'),newPromise((resolve,reject)=>setTimeout(()=>reject(newError('requesttimeout')),1000))]).then(console.log).catch(console.error);如果请求足够快,就会得到请求的结果。1.5Promise常见问题串行执行promises并传递参数('file.txt','utf8').then(content1=>fs.writeFile('output.txt',content1)).then(()=>fs.readFile('file2.txt','utf8')).then(content2=>fs.writeFile('output.txt',content2,{flag:'a+'})).catch(error=>console.log(错误));在此示例中,我们读取Take文件1并将其写入输出文件。稍后,我们读取文件2并将其再次追加到输出文件中。如您所见,writeFilepromise返回文件的内容,您可以在下一个then子句中使用它。如何链接多个条件承诺?您可能希望跳过Promise链中的某些步骤。有两种方法可以做到这一点。consta=()=>newPromise((resolve)=>setTimeout(()=>{console.log('a'),resolve()},1e3));constb=()=>newPromise((resolve)=>setTimeout(()=>{console.log('b'),resolve()},2e3));constc=()=>newPromise((resolve)=>setTimeout(()=>{console.log('c'),resolve()},3e3));constd=()=>newPromise((resolve)=>{console.log(()=>{console.log('d'),resolve()},4e3));constshouldExecA=true;constshouldExecB=false;constshouldExecC=false;constshouldExecD=true;承诺。=>shouldExecC&&c()).then(()=>shouldExecD&&d()).then(()=>console.log('done'))如果运行代码示例,您会注意到只有a和d被按下预期执行。另一种方法是创建一个链,然后仅在以下情况下添加它们:constchain=Promise.resolve();if(shouldExecA)chain=chain.then(a);if(shouldExecB)chain=chain.then(b);if(shouldExecC)chain=chain.then(c);if(shouldExecD)chain=chain.then(d);chain.then(()=>console.log('done'));如何限制并行Promise?为此,我们需要以某种方式绑定Promise.all。假设您有许多并发请求要执行。如果使用Promise.all是不好的(特别是如果API是速率受限的)。因此,我们需要一个方法来限制Promise的数量,我们称之为promiseAllThrottled。//simulate10asynctasksthattakes5secondstocomplete.constrequests=Array(10).fill().map((_,i)=>()=>newPromise((resolve=>setTimeout(()=>{console.log(`exec'ingtask#${i}`),resolve(`task#${i}`);},5000))));promiseAllThrottled(requests,{concurrency:3}).then(console.log).catch(错误=>console.error('Oopssomethingwentwrong',错误));输出应如下所示:上面的代码将并发限制为3个并行执行的任务。实现promiseAllThrottled的一种方法是使用Promise.race来限制给定时间的活动任务数。/***SimilartoPromise.allbutaconcurrencylimit**@param{Array}iterableArrayofffunctionsthatreturnspromise*@param{Object}concurrencymaxnumberofparallelpromisesrunning*/functionpromiseAllThrottled(iterable,{concurrency=3}={}){constpromises=[];functionenqueue(current=0,队列=[]){//returnifdoneif(current===iterable.length){returnPromise.resolve();}//takeonepromisefromcollectionconstpromise=iterable[current];constactivatedPromise=promise();//addpromistothefinalresultarraypromises.push(activatedPromise);//addcurrentactivatedpromisetoqueueandremoveitwhendoneconstautoRemovePromise=activatedPromise.then(()=>{//removepromisefromthequeuewhendonereturnqueue.splice(queue.indexOf(autoRemovePromise),1);});//addpromisetothequeuequeue.push(autoRemovePromise);//ifqueuelength>=concurrency,waittofinishonepromise队列长度=queue.lengthenqueue(current+1,queue));}returnenqueue().then(()=>Promise.all(promises));}promiseAllThrottled一个一个地处理Promises它完成了Promise并将它们添加到队列中。如果队列小于并发限制,它将继续添加到队列中。一旦达到限制,我们使用Promise.race等待一个承诺完成,因此它可以被一个新的替换。这里的技巧是当承诺自动履行时,承诺会自动从队列中删除。此外,我们使用race来检测承诺何时履行并添加新的承诺。