JavaScript异步编程的Promise是一种比较好的异步编程统一方法。如果直接使用传统的回调函数完成复杂的操作,就会形成一个回调深渊//回调深渊$.get('/url1'()=>{$.get('/url2'()=>{$.get('/url3'()=>{$.get('/url4'()=>{$.get('/url5'()=>{//大概是这样})})})})})CommonJS社区提出了Promise规范,在ES2015中被标准化,成为一种语言规范。当等待状态重编程成功或失败时,不能再改变。成功时触发onFulfilled回调,失败时触发onRejected回调。Promise只需使用newPromise传入回调函数即可。这个回调函数有两个参数,第一个参数将Promise变为成功状态,第二个参数将Promise变为失败状态。您可以使用.then和.catch方法来捕获成功和异常。这两个方法同样返回一个Promise对象//constpromsie=newPromise((resolve,reject)=>{reject(1)})promsie.then((value)=>{console.log(value)},(err)=>{//Executethisconsole.log(err)})//end会先执行console.log('end'),不管Promise中是否有异步操作,then方法中的回调函数还是会进入回调队列,等待同步代码写一个请求函数,用Promisefunctionajax(url){returnnewPromise((resove,reject)=>{varxhr=newXMLHttpRequest()xhr.open('GET',url)//new方法可以直接接受一个j对象xhr.responseType='json'xhr.onload=function(){if(this.status===200){resolve(this.response)}else{reject(newError(this.statusText))}}xhr.send()})}ajax('/json1.json').then(ret=>{console.log(ret)}).catch(err=>{console.log(err)})如果需要多次连续请求,可以使用chaincallajax('/json1.json').then(ret=>{returnajax('/json2.json')}).then(ret=>{returnajax('/json3.json')}).then(ret=>{returnajax('/json4.json')})这种链式调用是不是很眼熟?jquery中也有链式调用。在jquery中返回的是对象本身,所以可以实现链式调用。那么在Promise中是这样的吗?让promsie1=ajax('/json1.json')让promise2=promise1.then(ret=>{console.log(ret)}).catch(err=>{console.log(err)})console.log(promsie1===promise2)//falseleta=$("body").attr('class','body')letb=a.prop('disabled',true)console.log(a===b)//true经过测试,发现Promise返回了一个全新的Promise对象。返回一个全新的Promise对象的目的是为了实现Promise链。每个.then方法负责不同的任务,互不干扰。在链中调用then方法。这里的每一个then方法都是在stateclear之后给上一个then方法返回的Promise对象添加一个回调。这些Promise会依次执行,我们可以在then方法中手动返回一个Promise回调如果then方法中的回调函数有返回值,则将返回值传递给下一个then方法的回调函数。如果不返回,则默认返回未定义。综上所述,Promise对象的then方法会返回一个全新的Promise对象,then方法后面就是为之前的then返回的Promise注册回调。前面then方法中回调函数的返回值作为后面then方法回调的参数返回。如果回调返回一个Promise,那么后续then方法的回调会等待其结束捕获异常,当Promise执行异常或抛出异常时会触发onRejected回调。有两种方法可以捕获异常。第一个,then(成功处理的回调函数,异常处理的回调函数)在then方法中传入两个回调函数,第二个使用.catch方法捕获异常,catch方法其实就是一个别名then方法的,相当于传递undefined作为then方法的第一个参数//then(处理成功的回调函数,异常处理回调函数)ajax('/json1.json').then(ret=>{console.log(err)},err=>{console.log(err)})//catchajax('/json1.json').then(ret=>{console.log(err)}).catch(err=>{console.log(err)})//catchajax('/json1.json').then(ret=>{console.log(err)}).then(undefined,err=>{console.log(err)})这两种方法还是有很大区别的。catch其实就是对最后一个then返回的Promise的异常进行捕获,但是如果同一个链下的Promise的error会一直往下传递,直到被catch方法捕获,传递两个回调函数捕获异常的方法会只捕获前面Promiseajax('/json1.json').then(ret=>{console.log(ret)}).then(undefined,err=>{console.log(err)})的错误。然后(重新t=>{console.log(ret)}).then(ret=>{console.log(ret)})//catch捕获异常ajax('/json1.json').then(ret=>{console.log(ret)}).catch(err=>{//在这里你可以捕获所有以前的Promise异常})//然后传递第二个参数来捕获异常ajax('/json1.json').then(ret=>{console.log(ret)}).then(undefined,err=>{console.log(err)thrownewError('intentionalexception')},(err)=>{//cancatchintentionalexceptionhereError}).then(ret=>{console.log(ret)}).then(ret=>{console.log(ret)}).catch(err=>{//此时无法捕获异常,因为之前故意的异常已经被捕获了,根据then方法会返回一个Promise,所以捕获异常后,会返回一个成功的Promise})也可以全局捕获异常,不推荐这种全局捕获异常的方式,你应该在代码块中明确捕获相应的异常//在浏览器环境中window.addEventListener('unhandledrejection',event=>{console.log(event.reason,event.promise)//失败原因,//promiseFailedPromiseevent.preventDefault()},false)//process.on('unhandledRejection',(reason,promise)=>{console.log(reason,promise)innodejs//reasonoffailure,//promisefailurePromise})如果需要无论成功错误都执行,可以使用finally实现ajax('/json1.json')。then(ret=>{console.log('执行成功')}).catch(err=>{console.log("执行失败")}).finally(function(){console.log("成功和失败都会执行这个")});Promise静态方法Promise.resolve快速将一个值转换为Promise对象,该方法相当于newPromise返回一个值Promise.resolve({data:"hahah"})newPromise((resolve)=>{resolve({data:"hahah"})})如果传入一个Promise对象,它会原样返回对象functionajax(url){returnnewPromise((resolve,reject)=>{varxhr=newXMLHttpRequest()xhr.open('GET',url)//新方法可以直接接受一个j对象xhr.responseType='json'xhr.onload=function(){if(this.status===200){resolve(this.response)}else{reject(newError(this.statusText))}}xhr.send()})}letpromise1=ajax('/url')letpromise2=Promise.resolve(promise1)console.log(promise1===promise2)//true如果传入一个对象,并且这个对象也有类似Promise的then方法,也就是说这方也可以收到ToonFulfilled,onRejected两个回调,并且可以调用回调传递参数,这种与then方法配对像实现一个thenable接口,之所以支持这种对象,是因为在原生Promise普及之前,往往是通过第三方库来实现的。PromisePromise.resolve({then(onFulfilled,onRejected){onFulfilled('123')}}).then(ret=>{console.log(ret)//123})Promise.reject快速创建一个必须失败的Promise对象.这个方法的参数就是Promise失败的原因。Promise.reject("嘿,这是错误的原因").catch(err=>{console.log(err)//嘿,这是错误的原因})Promise.all接收一个数组,这些元素都是Promise对象,这个方法会返回一个新的Promise对象,Promise.all返回的Promise对象会在内部的Promise全部完成时完成。此时Promise.all返回的Promise对象的结果是一个数组,里面包含了每一个Promise返回的结果。值得注意的是,Promise.all返回的Promise对象只有在数组中的所有Promise都成功结束的情况下才会成功结束。如果数组中的Promise失败,则Promise.all返回的Promise也会失败。Promise.all([ajax('/url1'),ajax('/url2'),ajax('/url3'),ajax('/url4'),]).then(values=>{console.log(values)}).catch(err=>{console.log(err)})Promise.racewithPromise。all方法也接收一个数组,这些元素是一个Promise对象,这个方法会返回一个全新的Promise对象,但是和Promise.all方法的区别在于Promise.all等待所有任务结束,Promise.race只等待第一个完成的任务结束constrequest=ajax('/api/???')consttimeout=newPromise((resolve,reject)=>{setTimeout(()=>reject('timeout'),5000);})Promise.race([request,timeout]).then(ret=>{console.log(ret)}).catch(err=>{console.log(err)})在上面的代码中,如果接口在5秒之前返回,那么我们可以正常获取返回结果,如果5秒之内没有返回,那么请求就没有办法返回结果了,因为timeout的promise在5秒之后就失效了,并且Promise.race以第一个结束的Promise结束。Promise.allSettled与Promise.all和Promise.race方法一样,也接收一个数组。这些元素都是Promise对象。该方法会返回一个全新的Promise对象,与它们不同的是,无论这些Promise执行成功与否,都是在这些Promise执行完成后才完成。当有多个互不依赖的异步任务成功完成,或者总是想知道每个promise的结果时,通常使用constpromise1=Promise.r解决(3);constpromise2=newPromise((resolve,reject)=>setTimeout(reject,100,'foo'));constpromises=[promise1,promise2];承诺。全部解决(承诺)。then((results)=>results.forEach((result)=>console.log(result.status)));//>"fulfilled"//>"rejected"Promise.any接收一个像Promise.race这样的数组方法,这些元素都是Promise对象。此方法将返回一个全新的Promise对象。不同的是只要有一个Promise执行成功就认为成功,只有全部失败才算失败。新Promise的onFulfilled回调函数的参数是第一个成功完成的Promise传递过来的数据。constalwaysError=newPromise((resolve,reject)=>{reject("如果失败,下一个成功");});consttwo=newPromise((resolve,reject)=>{setTimeout(resolve,30,"我是第二个实现的Promise");});constthree=newPromise((resolve,reject)=>{setTimeout(resolve,70,"我是第三个要完成的Promise");});constone=newPromise((resolve,reject)=>{setTimeout(resolve,10,"我是第一个完成Promise");});Promise.any([two,three,alwaysError,one]).then((value)=>{console.log(value);//我是第一个完成的Promise//这个值是第一个完成的Promise传过来的值=>我是第一个完成的Promise})Promise执行时序问题宏任务、微任务测试执行顺序console.log('globalstart')Promise.resolve().then(ret=>{console.log('promise')})console.log('globalend')//outlog//1.globalstart//2.globalend//3.Promise链调用多次执行看执行顺序console.log('globalstart')Promise.resolve().then(ret=>{console.日志('promise1')})。然后(ret=>{console.log('promise2')}).then(ret=>{console.log('promise3')})console.log('globalend')//outlog//1.全局开始//2.全局结束//3.promise1//4.promise2//5.promise3addsetTimeoutconsole.log('globalstart')setTimeout(()=>{console.log('settimeout')},0);Promise.resolve().then(ret=>{console.log('promise1')}).then(ret=>{console.log('promise2')}).then(ret=>{console.log('promise3')})控制台。log('globalend')//1.globalstart//2.globalend//3.promise1//4.promise2//5.promise3//6.settimeout没想到Promise的异步定时执行的优势特别比如我们去银行的ATM取款,交易完突然想起要转账,这时候我们肯定会直接办理转账业务,不会再排队等候转账。在这个例子中,我们像在javascipt中等待执行的任务一样排队。我们队列中的每个人都对应回调队列中的一个任务。回调队列中的任务称为宏任务,在宏任务执行过程中可以临时加入一些额外的需求,这些额外的需求可以被选为一个新的宏任务,在队列中排队。上面的setTimeout会在回调队列中作为宏任务再次排队,也可以像我们前面的例子一样,在当前任务结束后直接作为当前任务的微任务执行。Promise的回调会作为一个微任务执行,在本轮调用结束时执行,所以上面的代码在打印settimeout时会先打印promise1、promise2、promise3。microtask是后来被js引入的,它的目的是为了提高整体的响应能力,目前的异步调用大多都是作为宏任务来执行的。Promise、MutationObserver和nodejs中的process.nextTick将在本轮调用结束时作为微任务执行。
