这是第 77 篇不掺水的原创,想获取更多原创好文,请搜索公众号关注咱们吧~ 本文首发于政采云前端博客: 编写高质量可维护的代码——异步优化
在如今前端开发中,异步操做的频次已经愈来愈高了,特别对于数据接口请求和定时器的使用,使得咱们不得不关注异步在业务中碰到的场景,以及对异步的优化。错误的异步处理可能会带来不少问题,诸如页面渲染、重复加载等问题。javascript
下面咱们就先简单的从 JavaScript 中有大体的哪几种异步类型为切入点,而后再列举一些业务中咱们会碰到的场景来逐个分析下,咱们该如何解决。html
首先关于异步实现的方式上大体有以下几种:前端
callback 即回调函数。这家伙出现很早很早了,他实际上是处理异步的基本方法。而且回调的概念不仅仅出如今JavaScript,你也会在 Java 或者 C# 等后端语言中也能找到他的影子。java
回调函数简单的说其实就是给另一个寄主函数做为传参的函数。在寄主函数执行完成或者执行到特定阶段以后触发调用回调函数并执行,而后把执行结果再返回给寄主函数的过程。node
好比咱们熟悉的 setTimeout 或者 React 中的 setState 的第二个方法都是以回调函数方式去解决异步的实现。编程
setTimeout(() => { //等待0.2s以后再作具体的业务操做 this.doSomething(); }, 200); this.setState({ count: res.count, }, () => { //在更新完count以后再作具体的业务操做 this.doSomething(); });
Promise 是个好东西,有了它以后咱们能够对异步进行不少操做,而且能够把异步以链式的方式进行操做。后端
其实在 JQuery 中的 deferred 和它就有点像,都是采用回调函数的解决方案,均可以作链式调用,可是在Promise 中增长了错误的 catch 方法能够更加方便的处理异常场景,而且它内置状态(resolve, reject,pending),状态只能由 pending 变为另外两种的其中一种,且改变后不可逆也不可再度修改。promise
let promise = new Promise((resolve, reject) => { reject("对不起,你不是个人菜"); }); promise.then((data) => { console.log('第一次success' + data); return '第一次success' + data },(error) => { console.log(error) } ).then((data2) => { console.log('第二次success' + data2); },(error2) => { console.log(error2) } ).catch((e) => { console.log('抓到错误啦' + e); });
await/async 实际上是 Promise 的一种升级版本,使用 await/async 调用异步的时候是从上到下,顺序执行,就像在写同步代码同样,这更加的符合咱们编写代码的习惯和思惟逻辑,因此容易理解。 总体代码逻辑也会更加的清晰。异步
async function asyncDemoFn() { const data1 = await getData1(); const data2 = await getData2(data1); const data3 = await getData3(data2); console.log(data3) } await asyncDemoFn()
generator 中文名叫构造器,是 ES6 中的一个新东西,我相信不少人在现实的代码中不多能接触到它,因此它相对而言对你们来讲仍是比较晦涩,可是这家伙仍是很强的,简单来讲它能控制异步调用,而且实际上是一个状态机。async
function* foo() { for (let i = 1; i <= 3; i++) { let x = yield `等我一下呗,i = ${i}`; console.log(x); } } setTimeout(() => { console.log('终于轮到我了'); }, 1); var a = foo(); console.log(a); // foo {<closed>} var b = a.next(); console.log(b); // {value: "等我一下呗,i = 1", done: false} var c = a.next(); console.log(c); // {value: "等我一下呗,i = 2", done: false} var d = a.next(); console.log(d); // {value: "等我一下呗,i = 3", done: false} var e = a.next(); console.log(e); // {value: undefined, done: true} // 终于轮到我了
上面代码的函数 foo 是一个协程,它的厉害的地方就是 yield 命令。它表示执行到此处,执行权将交给其余协程。也就是说,yield 命令是异步两个阶段的分界线。
协程遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续日后执行。它的最大优势,就是代码的写法很是像同步操做,若是去除 yield 命令,简直如出一辙。
再来个有点贴近点场景方式来使用下 generator。好比如今在页面中咱们须要自动的执行 checkAuth 和checkAddress 检查,咱们就用 generator 的方式去实现自动检查上述两异步检查。
const checkAuth = () => { return new Promise((resolve)=>{ setTimeout(()=>{ resolve('checkAuth1') },1000) }) } const checkAddress = () => { return new Promise((resolve)=>{ setTimeout(()=>{ resolve('checkAddress2') },2000) }) } var steps = [checkAuth,checkAddress] function* foo(checkList) { for (let i = 0; i < checkList.length; i++) { let x = yield checkList[i](); console.log(x); } } var stepsGen = foo(steps) var run = async (gen)=>{ var isFinnish = false do{ const {done,value} = gen.next() console.log('done:',done) console.log('value:',value) const result = await value console.log('result:',result) isFinnish = done }while(!isFinnish) console.log('isFinnish:',isFinnish) } run(stepsGen)
在使用回调函数的时候咱们可能会有这样的场景,B 须要在 A 的返回以后再继续调用,因此在这样有前后关系的时候就存在了一个叫回调地狱的问题了。
getData1().then((resData1) => { getData2(resData1).then((resData2) => { getData3(resData2).then((resData3)=>{ console.log('resData3:', resData3) }) }); });
碰到这样的状况咱们能够试着用 await/async 方式去解这种有多个深层嵌套的问题。
async function asyncDemoFn2() { const resData1 = await getData1(); const resData2 = await getData2(resData1); const resData3 = await getData3(resData2); console.log(resData3) } await asyncDemoFn2()
在业务中咱们最最常常碰到的就是其实仍是存在多个异步调用的顺序问题,大体上能够分为以下几种:
在并行执行的时候,咱们能够直接使用 Promise 的 all 方法
Promise.all([getData1(),getData2(),getData3()]).then(res={ console.log('res:',res) })
在顺序执行中,咱们能够有以下的两种方式去作
const sources = [getData1,getData2,getData3] async function promiseQueue() { console.log('开始'); for (let targetSource in sources) { await targetSource(); } console.log('完成'); }; promiseQueue()
//getData1,getData2,getData3 都为promise对象 const sources = [getData1,getData2,getData3] async function promiseQueue() { let index = 0 console.log('开始'); while(index >=0 && index < sources.length){ await targetSource(); index++ } console.log('完成'); }; promiseQueue()
//getData1,getData2,getData3 都为promise对象 const sources = [getData1,getData2,getData3] sources.reduce(async (previousValue, currentValue)=>{ await previousValue return currentValue() },Promise.resolve())
const sources = [getData1,getData2,getData3] function promiseQueue(list , index = 0) { const len = list.length console.log('开始'); if(index >= 0 && index < len){ list[index]().then(()=>{ promiseQueue(list, index+1) }) } console.log('完成'); } promiseQueue(sources)
今天只是关于异步的普通使用场景的讨论,而且作了些简单的例子。其实关于异步的使用还有不少不少复杂的使用场景。更多的奇思妙想正等着你。
政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 40 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在平常的业务对接以外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推进并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。
若是你想改变一直被事折腾,但愿开始能折腾事;若是你想改变一直被告诫须要多些想法,却无从破局;若是你想改变你有能力去作成那个结果,却不须要你;若是你想改变你想作成的事须要一个团队去支撑,但没你带人的位置;若是你想改变既定的节奏,将会是“5 年工做时间 3 年工做经验”;若是你想改变原本悟性不错,但老是有那一层窗户纸的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但愿参与到随着业务腾飞的过程,亲手推进一个有着深刻的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我以为咱们该聊聊。任什么时候间,等着你写点什么,发给 ZooTeam@cai-inc.com