此文只介绍Async/Await与Promise基础知识与实际用到注意的问题,将经过不少代码实例进行说明,两个实例代码是setDelay
和setDelaySecond
。
tips:本文系原创转自个人博客异步Promise及Async/Await最完整入门攻略,欢迎前端大神交流,指出问题html
咱们都知道已经有了Promise
的解决方案了,为何还要ES7提出新的Async/Await标准呢?前端
答案其实也显而易见:Promise
虽然跳出了异步嵌套的怪圈,用链式表达更加清晰,可是咱们也发现若是有大量的异步请求的时候,流程复杂的状况下,会发现充满了屏幕的then
,看起来很是吃力,而ES7的Async/Await的出现就是为了解决这种复杂的状况。node
首先,咱们必须了解Promise
。git
什么是Promise,不少人应该都知道基础概念?直接看下面的代码(全文的例子都是基于setDelaySecond
和setDelay
两个函数,请务必记住):es6
const setDelay = (millisecond) => { return new Promise((resolve, reject)=>{ if (typeof millisecond != 'number') reject(new Error('参数必须是number类型')); setTimeout(()=> { resolve(`我延迟了${millisecond}毫秒后输出的`) }, millisecond) }) }
咱们把一个Promise封装在一个函数里面同时返回了一个Promise,这样比较规范。github
能够看到定义的Promise有两个参数,resolve
和reject
。web
resolve
:将异步的执行从pending(请求)
变成了resolve(成功返回)
,是个函数执行返回。reject
:顾名思义“拒绝”,就是从请求变成了"失败",是个函数能够执行返回一个结果,但咱们这里推荐你们返回一个错误new Error()
。
上述例子,你能够
reject('返回一个字符串')
,随便你返回,可是咱们仍是
建议返回一个Error对象,这样更加清晰是“失败的”,这样更规范一点。
咱们经过Promise的原型方法then
拿到咱们的返回值:segmentfault
setDelay(3000) .then((result)=>{ console.log(result) // 输出“我延迟了2000毫秒后输出的” })
输出下列的值:“我延迟了2000毫秒后输出的”。数组
若是出错呢?那就用catch
捕获:promise
setDelay('我是字符串') .then((result)=>{ console.log(result) // 不进去了 }) .catch((err)=>{ console.log(err) // 输出错误:“参数必须是number类型” })
是否是很简单?好,如今我增长一点难度,若是多个Promise
执行会是怎么样呢?
咱们在写一个Promise:
const setDelaySecond = (seconds) => { return new Promise((resolve, reject)=>{ if (typeof seconds != 'number' || seconds > 10) reject(new Error('参数必须是number类型,而且小于等于10')); setTimeout(()=> { console.log(`先是setDelaySeconds函数输出,延迟了${seconds}秒,一共须要延迟${seconds+2}秒`) resolve(setDelay(2000)) // 这里依赖上一个Promise }, seconds * 1000) }) }
在下一个须要依赖的resolve
去返回另外一个Promise,会发生什么呢?咱们执行一下:
setDelaySecond(3).then((result)=>{ console.log(result) }).catch((err)=>{ console.log(err); })
你会发现结果是先执行:“先是setDelaySeconds输出,延迟了2秒,一共须要延迟5秒”
再执行setDelay
的resolve
:“我延迟了2000毫秒后输出的”。的确作到了依次执行的目的。
有人说,我不想耦合性这么高,想先执行setDelay
函数再执行setDelaySecond
,但不想用上面那种写法,能够吗,答案是固然能够。
先改写一下setDelaySecond
,拒绝依赖,下降耦合性
const setDelaySecond = (seconds) => { return new Promise((resolve, reject)=>{ if (typeof seconds != 'number' || seconds > 10) reject(new Error('参数必须是number类型,而且小于等于10')); setTimeout(()=> { resolve(`我延迟了${seconds}秒后输出的,是第二个函数`) }, seconds * 1000) }) }
先执行setDelay
在执行setDelaySecond
,只须要在第一个then
的结果中返回下一个Promise就能够一直链式写下去了,至关于依次执行:
setDelay(2000) .then((result)=>{ console.log(result) console.log('我进行到第一步的'); return setDelaySecond(3) }) .then((result)=>{ console.log('我进行到第二步的'); console.log(result); }).catch((err)=>{ console.log(err); })
发现确实达到了可喜的链式(终于脱离异步嵌套苦海,哭),能够看到then
的链式写法很是优美。
这里必定要提到一点:
then
式链式写法的本质实际上是一直往下传递返回一个新的Promise,也就是说then在下一步接收的是上一步返回的Promise,理解这个对于后面的细节很是重要!!
那么并非这么简单,then的返回咱们能够看出有2个参数(都是回调):
Promise
成功resolve
的值。咱们修改上面的代码:
setDelay(2000) .then((result)=>{ console.log(result) console.log('我进行到第一步的'); return setDelaySecond(20) }) .then((result)=>{ console.log('我进行到第二步的'); console.log(result); }, (_err)=> { console.log('我出错啦,进到这里捕获错误,可是不通过catch了'); }) .then((result)=>{ console.log('我仍是继续执行的!!!!') }) .catch((err)=>{ console.log(err); })
能够看到输出结果是:进到了then
的第二个参数(reject)中去了,并且最重要的是!再也不通过catch
了。
那么咱们把catch挪上去,写到then
错误处理前:
setDelay(2000) .then((result)=>{ console.log(result) console.log('我进行到第一步的'); return setDelaySecond(20) }) .catch((err)=>{ // 挪上去了 console.log(err); // 这里catch到上一个返回Promise的错误 }) .then((result)=>{ console.log('我进行到第二步的'); console.log(result); }, (_err)=> { console.log('我出错啦,可是因为catch在我前面,因此错误早就被捕获了,我这没有错误了'); }) .then((result)=>{ console.log('我仍是继续执行的!!!!') })
能够看到先通过catch
的捕获,后面就没错误了。
能够得出须要注意的:
catch
写法是针对于整个链式写法的错误捕获的,而then
第二个参数是针对于上一个返回Promise
的。break
, 能够继续执行后续操做不受影响。上述已经写好了关于then里面三个回调中第二个回调(reject)会与catch冲突的问题,那么咱们实际写的时候,参数捕获的方式基本写得少,catch的写法会用到更多。
既然有了不少的Promise,那么我需不须要写不少catch呢?
答案固然是:不须要!,哪有那么麻烦的写法,只须要在末尾catch
一下就能够了,由于链式写法的错误处理具备“冒泡”特性,链式中任何一个环节出问题,都会被catch
到,同时在某个环节后面的代码就不会执行了。
既然说到这里,咱们把catch
移到第一个链式的返回里面会发生什么事呢?看下面代码:
setDelay('2000') .then((result)=>{ console.log('第一步完成了'); console.log(result) return setDelaySecond(3) }) .catch((err)=>{ // 这里移到第一个链式去,发现上面的不执行了,下面的继续执行 console.log(err); }) .then((result)=>{ console.log('第二步完成了'); console.log(result); })
惊喜的发现,链式继续走下去了!!输出以下(undefined是由于上一个then没有返回一个Promise):
重点来了!敲黑板!!链式中的catch
并非终点!!catch完若是还有then还会继续往下走!不信的话能够把第一个catch
在最后面的那个例子后面再加几个then
,你会发现并不会跳出链式执行。
若是顺序执行setDelay,setDelay1,setDelaySecond
,按照上述的逻辑,流程图能够归纳以下:
catch
只是捕获错误的一个链式表达,并非break!
因此,catch放的位置也颇有讲究,通常放在一些重要的、必须catch的程序的最后。**这些重要的程序中间一旦出现错误,会立刻跳过其余后续程序的操做直接执行到最近的catch代码块,但不影响catch后续的操做!!!!
到这就不得不体一个ES2018标准新引入的Promise的finally
,表示在catch后必须确定会默认执行的的操做。这里很少展开,细节能够参考:Promise的finally
其实很简单,用Promise
的原型方法resolve
便可:
setDelay(2000).then((result)=>{ console.log('第一步完成了'); console.log(result); let message = '这是我本身想处理的值'; return Promise.resolve(message) // 这里返回我想在下一阶段处理的值 }) .then((result)=>{ console.log('第二步完成了'); console.log(result); // 这里拿到上一阶段的返回值 //return Promise.resolve('这里能够继续返回') }) .catch((err)=>{ console.log(err); })
不一样于通常的function
的break
的方式,若是你是这样的操做:func().then().then().then().catch()
的方式,你想在第一个then
就跳出链式,后面的不想执行了,不一样于通常的break;return null;return false
等操做,能够说,如何中止Promise链,是一大难点,是整个Promise最复杂的地方。
1.用链式的思惟想,咱们拒绝掉某一链,那么不就是至关于直接跳到了catch模块吗?
咱们是否是能够直接“拒绝“掉达到中止的目的?
setDelay(2000) .then((result)=>{ console.log(result) console.log('我进行到第一步的'); return setDelaySecond(1) }) .then((result)=>{ console.log('我进行到第二步的'); console.log(result); console.log('我主动跳出循环了'); return Promise.reject('跳出循环的信息') // 这里返回一个reject,主动跳出循环了 }) .then((result)=>{ console.log('我不执行'); }) .catch((mes)=>{ console.dir(mes) console.log('我跳出了'); })
可是很容易看到缺点:有时候你并不肯定是由于错误跳出的,仍是主动跳出的,因此咱们能够加一个标志位:
return Promise.reject({ isNotErrorExpection: true // 返回的地方加一个标志位,判断是不是错误类型,若是不是,那么说明能够是主动跳出循环的 })
或者根据上述的代码判断catch的地方输出的类型是否是属于错误对象的,是的话说明是错误,不是的话说明是主动跳出的,你能够本身选择(这就是为何要统一错误reject的时候输出new Error('错误信息')的缘由,规范!)
固然你也能够直接抛出一个错误跳出:
throw new Error('错误信息') // 直接跳出,那就不能用判断是否为错误对象的方法进行判断了
2.那有时候咱们有这个需求:catch是放在中间(不是末尾),而同时咱们又不想执行catch后面的代码,也就是链式的绝对停止,应该怎么办?
咱们看这段代码:
setDelay(2000) .then((result)=>{ console.log(result) console.log('我进行到第一步的'); return setDelaySecond(1) }) .then((result)=>{ console.log('我进行到第二步的'); console.log(result); console.log('我主动跳出循环了'); return Promise.reject('跳出循环的信息') // 这里直接调用Promise原型方法返回一个reject,主动跳出循环了 }) .then((result)=>{ console.log('我不执行'); }) .catch((mes)=>{ console.dir(mes) console.log('我跳出了'); }) .then((res)=>{ console.log('我不想执行,可是却执行了'); // 问题在这,上述的终止方法治标不治本。 })
这时候最后一步then
仍是执行了,整条链都其实没有本质上的跳出,那应该怎么办呢?
敲黑板!!重点来了!咱们看Promise/A+规范能够知道:
A promise must be in one of three states: pending, fulfilled, or rejected.
Promise实际上是有三种状态的:pending,resolve,rejected,那么咱们一直在讨论resolve和rejected
这2个状态,是否是忽视了pending
这个状态呢?pending状态顾名思义就是请求中的状态,成功请求就是resolve,失败就是reject,其实他就是个中间过渡状态。
而咱们上面讨论过了,then
的下一层级其实获得的是上一层级返回的Promise对象,也就是说原Promise对象与新对象状态保持一致。那么重点来了,若是你想在这一层级进行终止,是否是直接让它永远都pending
下去,那么后续的操做不就没了吗?是否是就达到这个目的了??以为有疑问的能够参考Promise/A+规范。
咱们直接看代码:
setDelay(2000) .then((result)=>{ console.log(result) console.log('我进行到第一步的'); return setDelaySecond(1) }) .then((result)=>{ console.log(result); console.log('我主动跳出循环了'); // return Promise.reject('跳出循环的信息') // 重点在这 return new Promise(()=>{console.log('后续的不会执行')}) // 这里返回的一个新的Promise,没有resolve和reject,那么会一直处于pending状态,由于没返回啊,那么这种状态就一直保持着,中断了这个Promise }) .then((result)=>{ console.log('我不执行'); }) .catch((mes)=>{ console.dir(mes) console.log('我跳出了'); }) .then((res)=>{ console.log('我也不会执行') })
这样就解决了上述,错误跳出而致使没法彻底终止Promise链的问题。
可是!随之而来也有一个问题,那就是可能会致使潜在的内存泄漏,由于咱们知道这个一直处于pending状态下的Promise会一直处于被挂起的状态,而咱们具体不知道浏览器的机制细节也不清楚,通常的网页没有关系,但大量的复杂的这种pending状态势必会致使内存泄漏,具体的没有测试过,后续可能会跟进测试(nodeJS或webapp里面不推荐这样),而我经过查询也难以找到答案,这篇文章能够推荐看一下:从如何停掉 Promise 链提及。可能对你有帮助在此种状况下如何作。
固然通常状况下是不会存在泄漏,只是有这种风险,没法取消Promise一直是它的痛点。而上述两个奇妙的取消方法要具体情形具体使用。
其实这几个方法就简单了,就是一个简写串联全部你须要的Promise
执行,具体能够参照阮一峰的ES6Promise.all教程。
我这上一个代码例子
Promise.all([setDelay(1000), setDelaySecond(1)]).then(result=>{ console.log(result); }) .catch(err=>{ console.log(err); }) // 输出["我延迟了1000毫秒后输出的", "我延迟了1秒后输出的,注意单位是秒"]
输出的是一个数组,至关于把all
方法里面的Promise
并行执行,注意是并行。
至关于两个Promise同时开始执行,同时返回值,并非先执行第一个再执行第二个,若是你想串行执行,请参考我后面写的循环Promise循环串行(第4.2小节)。
而后把resolve的值保存在数组中输出。相似的还有Promise.race这里就很少赘述了。
什么是async/await
呢?能够总结为一句话:async/await是一对好基友,缺一不可,他们的出生是为Promise服务的。能够说async/await是Promise的爸爸,进化版。为何这么说呢?且听我细细道来。
为何要有async/await
存在呢?
前文已经说过了,为了解决大量复杂不易读的Promise异步的问题,才出现的改良版。
这两个基友必须同时出现,缺一不可,那么先说一下Async
:
async function process() { }
上面能够看出,async
必须声明的是一个function,不要去声明别的,要是那样await
就不理你了(报错)。
这样声明也是错的!
const async demo = function () {} // 错误
必须紧跟着function
。接下来讲一下它的兄弟await
。
上面说到必须是个函数(function),那么await
就必须是在这个async
声明的函数内部使用,不然就会报错。
就算你这样写,也是错的。
let data = 'data' demo = async function () { const test = function () { await data } }
必须是直系(做用域链不能隔代),这样会报错:Uncaught SyntaxError: await is only valid in async function
。
讲完了基本规范,咱们接下去说一下他们的本质。
敲黑板!!!很重要!async声明的函数的返回本质上是一个Promise。
什么意思呢?就是说你只要声明了这个函数是async
,那么内部无论你怎么处理,它的返回确定是个Promise。
看下列例子:
(async function () { return '我是Promise' })() // 返回是Promise //Promise {<resolved>: "我是Promise"}
你会发现返回是这个:Promise {<resolved>: "我是Promise"}
。
自动解析成Promise.resolve('我是Promise');
等同于:
(async function () { return Promise.resolve('我是Promise'); })()
因此你想像通常function
的返回那样,拿到返回值,原来的思惟要改改了!你能够这样拿到返回值:
const demo = async function () { return Promise.resolve('我是Promise'); // 等同于 return '我是Promise' // 等同于 return new Promise((resolve,reject)=>{ resolve('我是Promise') }) } demo.then(result=>{ console.log(result) // 这里拿到返回值 })
上述三种写法都行,要看注释细节都写在里面了!!像对待Promise同样去对待async的返回值!!!
好的接下去咱们看await
的干吗用的.
await的本质是能够提供等同于”同步效果“的等待异步返回能力的语法糖。
这一句咋一看很别扭,好的不急,咱们从例子开始看:
const demo = async ()=>{ let result = await new Promise((resolve, reject) => { setTimeout(()=>{ resolve('我延迟了一秒') }, 1000) }); console.log('我因为上面的程序还没执行完,先不执行“等待一会”'); } // demo的返回当作Promise demo().then(result=>{ console.log('输出',result); })
await顾名思义就是等待一会,只要await
声明的函数尚未返回,那么下面的程序是不会去执行的!!!。这就是字面意义的等待一会(等待返回再去执行)。
那么你到这测试一下,你会发现输出是这个:输出 undefined
。这是为何呢?这也是我想强调的一个地方!!!
你在demo
函数里面都没声明返回,哪来的then
?因此正确写法是这样:
const demo = async ()=>{ let result = await new Promise((resolve, reject) => { setTimeout(()=>{ resolve('我延迟了一秒') }, 1000) }); console.log('我因为上面的程序还没执行完,先不执行“等待一会”'); return result; } // demo的返回当作Promise demo().then(result=>{ console.log('输出',result); // 输出 我延迟了一秒 })
我推荐的写法是带上then
,规范一点,固然你没有返回也是没问题的,demo
会照常执行。下面这种写法是不带返回值的写法:
const demo = async ()=>{ let result = await new Promise((resolve, reject) => { setTimeout(()=>{ resolve('我延迟了一秒') }, 1000) }); console.log('我因为上面的程序还没执行完,先不执行“等待一会”'); } demo();
因此能够发现,只要你用await声明的异步返回,是必须“等待”到有返回值的时候,代码才继续执行下去。
那事实是这样吗?你能够跑一下这段代码:
const demo = async ()=>{ let result = await setTimeout(()=>{ console.log('我延迟了一秒'); }, 1000) console.log('我因为上面的程序还没执行完,先不执行“等待一会”'); return result } demo().then(result=>{ console.log('输出',result); })
你会发现,输出是这样的:
我因为上面的程序还没执行完,先不执行“等待一会” 输出 1 我延迟了一秒
奇怪,并无await啊?setTimeout
是异步啊,问题在哪?问题就在于setTimeout
这是个异步,可是不是Promise
!起不到“等待一会”的做用。
因此更准确的说法应该是用await声明的Promise异步返回,必须“等待”到有返回值的时候,代码才继续执行下去。
固然这种等待的效果只存在于“异步”的状况,await能够用于声明通常状况下的传值吗?
事实是固然能够:
const demo = async ()=>{ let message = '我是声明值' let result = await message; console.log(result); console.log('我因为上面的程序还没执行完,先不执行“等待一会”'); return result } demo().then(result=>{ console.log('输出',result); })
输出:
我是声明值 我因为上面的程序还没执行完,先不执行“等待一会” 输出 我是声明值
这里只要注意一点:then
的执行老是最后的。
如今咱们看一下实战:
const setDelay = (millisecond) => { return new Promise((resolve, reject)=>{ if (typeof millisecond != 'number') reject(new Error('参数必须是number类型')); setTimeout(()=> { resolve(`我延迟了${millisecond}毫秒后输出的`) }, millisecond) }) } const setDelaySecond = (seconds) => { return new Promise((resolve, reject)=>{ if (typeof seconds != 'number' || seconds > 10) reject(new Error('参数必须是number类型,而且小于等于10')); setTimeout(()=> { resolve(`我延迟了${seconds}秒后输出的,注意单位是秒`) }, seconds * 1000) }) }
好比上面两个延时函数(写在上面),好比我想先延时1秒,在延迟2秒,再延时1秒,最后输出“完成”,这个过程,若是用then
的写法,大概是这样(嵌套地狱写法出门右拐不送):
setDelay(1000) .then(result=>{ console.log(result); return setDelaySecond(2) }) .then(result=>{ console.log(result); return setDelay(1000) }) .then(result=>{ console.log(result); console.log('完成') }) .catch(err=>{ console.log(err); })
咋一看是否是挺繁琐的?若是逻辑多了估计看得更累,如今咱们来试一下async/await
(async ()=>{ const result = await setDelay(1000); console.log(result); console.log(await setDelaySecond(2)); console.log(await setDelay(1000)); console.log('完成了'); })()
看!是否是没有冗余的长长的链式代码,语义化也很是清楚,很是舒服,那么你看到这里,必定还发现了,上面的catch
咱们是否是没有在async中实现?接下去咱们就分析一下async/await如何处理错误?
由于async函数返回的是一个Promise,因此咱们能够在外面catch
住错误。
const demo = async ()=>{ const result = await setDelay(1000); console.log(result); console.log(await setDelaySecond(2)); console.log(await setDelay(1000)); console.log('完成了'); } demo().catch(err=>{ console.log(err); })
在async函数的catch
中捕获错误,当作一个Pormise处理,同时你不想用这种方法,可使用try...catch
语句:
(async ()=>{ try{ const result = await setDelay(1000); console.log(result); console.log(await setDelaySecond(2)); console.log(await setDelay(1000)); console.log('完成了'); } catch (e) { console.log(e); // 这里捕获错误 } })()
固然这时候你就不须要在外面catch
了。
一般咱们的try...catch
数量不会太多,几个最多了,若是太多了,说明你的代码确定须要重构了,必定没有写得很是好。还有一点就是try...catch一般只用在须要的时候,有时候不须要catch错误的地方就能够不写。
有人会问了,我try...catch
好像只能包裹代码块,若是我须要拆分开分别处理,不想由于一个的错误就整个process都crash掉了,那么难道我要写一堆try...catch
吗?我就是别扭,我就是不想写try...catch
怎嘛办?下面有一种很好的解决方案,仅供参考:
咱们知道await后面跟着的确定是一个Promise
那是否是能够这样写?
(async ()=>{ const result = await setDelay(1000).catch(err=>{ console.log(err) }); console.log(result); const result1 = await setDelaySecond(12).catch(err=>{ console.log(err) }) console.log(result1); console.log(await setDelay(1000)); console.log('完成了'); })()
这样输出:
我延迟了1000毫秒后输出的 Error: 参数必须是number类型,而且小于等于10 at Promise (test4.html:19) at new Promise (<anonymous>) at setDelaySecond (test4.html:18) at test4.html:56 undefined 我延迟了1000毫秒后输出的 完成了
是否是就算有错误,也不会影响后续的操做,是否是很棒?固然不是,你说这代码也忒丑了吧,乱七八糟的,写得别扭await又跟着catch。那么咱们能够改进一下,封装一下提取错误的代码函数:
// to function function to(promise) { return promise.then(data => { return [null, data]; }) .catch(err => [err]); // es6的返回写法 }
返回的是一个数组,第一个是错误,第二个是异步结果,使用以下:
(async ()=>{ // es6的写法,返回一个数组(你能够改回es5的写法以为不习惯的话),第一个是错误信息,第二个是then的异步返回数据,这里要注意一下重复变量声明可能致使问题(这里举例是全局,若是用let,const,请换变量名)。 [err, result] = await to(setDelay(1000)) // 若是err存在就是有错,不想继续执行就抛出错误 if (err) throw new Error('出现错误,同时我不想执行了'); console.log(result); [err, result1] = await to(setDelaySecond(12)) // 还想执行就不要抛出错误 if (err) console.log('出现错误,同时我想继续执行', err); console.log(result1); console.log(await setDelay(1000)); console.log('完成了'); })()
首先咱们要明确的是,Promise
自己是没法停止的,Promise
自己只是一个状态机,存储三个状态(pending,resolved,rejected),一旦发出请求了,必须闭环,没法取消,以前处于pending状态只是一个挂起请求的状态,并非取消,通常不会让这种状况发生,只是用来临时停止链式的进行。
中断(终止)的本质在链式中只是挂起,并非本质的取消Promise
请求,那样是作不到的,Promise
也没有cancel
的状态。
不一样于Promise
的链式写法,写在async/await中想要中断程序就很简单了,由于语义化很是明显,其实就和通常的function
写法同样,想要中断的时候,直接return
一个值就行,null
,空,false
都是能够的。看例子:
let count = 6; const demo = async ()=>{ const result = await setDelay(1000); console.log(result); const result1 = await setDelaySecond(count); console.log(result1); if (count > 5) { return '我退出了,下面的不进行了'; // return; // return false; // 这些写法均可以 // return null; } console.log(await setDelay(1000)); console.log('完成了'); }; demo().then(result=>{ console.log(result); }) .catch(err=>{ console.log(err); })
实质就是直接return
返回了一个Promise
,至关于return Promise.resolve('我退出了下面不进行了')
,固然你也能够返回一个“拒绝”:return Promise.reject(new Error('拒绝'))
那么就会进到错误信息里去。
咱们常常会使用上述两种写法,也可能混用,有时候会遇到一些状况,这边举例子说明:
并行的不用多说,很简单,直接循环发出请求就能够或者用Promise.all
。若是咱们须要串行循环一个请求,那么应该怎么作呢?
咱们须要实现一个依次分别延迟1秒输出值,一共5秒的程序,首先是Promise的循环,这个循环就相对来讲比较麻烦:
先不说循环,咱们先举一个错误的例子,如今有一个延迟函数
const setDelay = (millisecond) => { return new Promise((resolve, reject)=>{ if (typeof millisecond != 'number') reject(new Error('参数必须是number类型')); setTimeout(()=> { resolve(`我延迟了${millisecond}毫秒后输出的`) }, millisecond) }) }
咱们想作到:“循环串行执行延迟一秒的Promise函数”,指望的结果应该是:隔一秒输出我延迟了1000毫秒后输出的
,一共通过循环3次。咱们想固然地写出下列的链式写法:
arr = [setDelay(1000), setDelay(1000), setDelay(1000)] arr[0] .then(result=>{ console.log(result) return arr[1] }) .then(result=>{ console.log(result) return arr[2] }) .then(result=>{ console.log(result) })
可是很不幸,你发现输出是并行的!!!也就是说一秒钟一次性输出了3个值!。那么这是什么状况呢?其实很简单。。。就是你把setDelay(1000)
这个直接添加到数组的时候,其实就已经执行了,注意你的执行语句(1000)
这实际上是基础,是语言的特性,不少粗心的人(或者是没有好好学习JS的人)会觉得这样就把函数添加到数组里面了,却不知函数已经执行过一次了。
那么这样致使的后果是什么呢?也就是说数组里面保存的每一个Promise
状态都是resolve
完成的状态了,那么你后面链式调用直接return arr[1]
其实没有去请求,只是当即返回了一个resolve的状态。因此你会发现程序是至关于并行的,没有依次顺序调用。
那么解决方案是什么呢?直接函数名存储函数的方式(不执行Promise)来达到目的
咱们这样改一下程序:
arr = [setDelay, setDelay, setDelay] arr[0](1000) .then(result=>{ console.log(result) return arr[1](1000) }) .then(result=>{ console.log(result) return arr[2](1000) }) .then(result=>{ console.log(result) })
上述至关于把Promise
预先存储在一个数组中,在你须要调用的时候,再去执行。固然你也能够用闭包的方式存储起来,须要调用的时候再执行。
上述写法是不优雅的,次数一多就GG了,为何要提一下上面的then
,其实就是为了后面的for
循环作铺垫。
上面的程序根据规律改写一下:
arr = [setDelay, setDelay, setDelay] var temp temp = arr[0](1000) for (let i = 1; i <= arr.length; i++) { if (i == arr.length) { temp.then(result=>{ console.log('完成了'); }) break; } temp = temp.then((result)=>{ console.log(result); return arr[i-1](1000) }); }
错误处理能够在for循环中套入try...catch
,或者在你每一个循环点进行.then().catch()
、都是可行的。若是你想提取成公共方法,能够再改写一下,利用递归的方式:
首先你须要闭包你的Promise
程序
function timeout(millisecond) { return ()=> { return setDelay(millisecond); } }
若是不闭包会致使什么后果呢?不闭包的话,你传入的参数值后,你的Promise会立刻执行,致使状态改变,若是用闭包实现的话,你的Promise会一直保存着,等到你须要调用的时候再使用。并且最大的优势是能够预先传入你须要的参数。
改写数组:
arr = [timeout(2000), timeout(1000), timeout(1000)]
提取方法,Promise
数组做为参数传入:
const syncPromise = function (arr) { const _syncLoop = function (count) { if (count === arr.length - 1) { // 是最后一个就直接return return arr[count]() } return arr[count]().then((result)=>{ console.log(result); return _syncLoop(count+1) // 递归调用数组下标 }); } return _syncLoop(0); }
使用:
syncPromise(arr).then(result=>{ console.log(result); console.log('完成了'); }) // 或者 添加到Promise类中方法 Promise.syncAll = function syncAll(){ return syncPromise }// 之后能够直接使用 Promise.syncAll(arr).then(result=>{ console.log(result); console.log('完成了'); })
还有大神总结了一个reduce
的写法,其实就是一个迭代数组的过程:
const p = arr.reduce((total, current)=>{ return total.then((result)=>{ console.log(result); return current() }) }, Promise.resolve('程序开始')) p.then((result)=>{ console.log('结束了', result); })
都是可行的,在Promise
的循环领域。
如今就来介绍一下牛逼的async/await实战,上述的代码你是否是要看吐了,的确,我也以为好麻烦啊,那么若是用async/await
能有什么改进吗?这就是它出现的意义:
模拟上述代码的循环:
(async ()=>{ arr = [timeout(2000), timeout(1000), timeout(1000)] for (var i=0; i < arr.length; i++) { result = await arr[i](); console.log(result); } })()
。。。这就完了?是的。。。就完了,是否是特别方便!!!!语义化也很是明显!!这里为了保持与上面风格一致,没有加入错误处理,因此实战的时候记得加入你的try...catch
语句来捕获错误。
一直想总结一下Promise
和async/await
,不少地方可能总结得不够,已经尽力扩大篇幅了,后续有新的知识点和总结点可能会更新(未完待续),可是入门这个基本够用了。
咱们常说什么async/await
的出现淘汰了Promise,能够说是大错特错,偏偏相反,正由于有了Promise,才有了改良版的async/await
,从上面分析就能够看出,二者是相辅相成的,缺一不可。
想学好async/await
必须先精通Promise
,二者密不可分,有不一样意见和改进的欢迎指导!
前端小白,你们互相交流,peace!