在现在前端开发中,异步操作的频次已经越来越高了,特别对于数据接口请求和定时器的使用,使得我们不得不关注异步在业务中碰到的场景,以及对异步的优化。错误的异步处理可能会带来很多问题,诸如页面渲染、重复加载等问题。
下面我们就先简单的从 JavaScript 中有大致的哪几种异步类型为切入点,然后再列举一些业务中我们会碰到的场景来逐个分析下,我们该如何解决。
首先关于异步实现的方式上大致有如下几种:
callback 即回调函数。这家伙出现很早很早了,他其实是处理异步的基本方法。并且回调的概念不单单出现在JavaScript,你也会在 Java 或者 C# 等后端语言中也能找到他的影子。
回调函数简单的说其实就是给另外一个寄主函数作为传参的函数。在寄主函数执行完成或者执行到特定阶段之后触发调用回调函数并执行,然后把执行结果再返回给寄主函数的过程。
比如我们熟悉的 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 变为另外两种的其中一种,且改变后不可逆也不可再度修改。
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 中的一个新东西,我相信很多人在现实的代码中很少能接触到它,所以它相对而言对大家来说还是比较晦涩,但是这家伙还是很强的,简单来说它能控制异步调用,并且其实是一个状态机。
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)
从时间维度从早到晚:callback,promise, generator,await/async
await/async 是目前对于异步的终极形式
callback 让我们有了基本的方式去处理异步情况,Promise 告别了 callback 的回调地狱并且增加 resolve,reject 和 catch 等方法让我们能处理不同的情况,generator 增加了对于异步的可操作性,类似一个状态机可暂时停住多个异步的执行,然后在合适的时候继续执行剩余的异步调用,await/async 让异步调用更加语义化,并且自动执行异步
在使用回调函数的时候我们可能会有这样的场景,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)
})
在顺序执行中,我们可以有如下的两种方式去做
使用 async/await 配合 for
const sources = [getData1,getData2,getData3]
async function promiseQueue() {
console.log('开始');
for (let targetSource in sources) {
await targetSource();
}
console.log('完成');
};
promiseQueue()
使用 async/await 配合 while
//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()
使用 async/await 配合 reduce
//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)
今天只是关于异步的普通使用场景的讨论,并且做了些简单的例子。其实关于异步的使用还有很多很多复杂的使用场景。更多的奇思妙想正等着你。
参考文献
JS 异步编程六种方案
Async/Await替代Promise的6个理由
Javascript异步编程的4种方法
本文来自:https://zoo.team/article/asynchronization-optimizing
在程序开发中,经常会使用到for循环的,但是很多人写的for循环效率都是比较低的,下面就举例说明,并总结优化for循环的方法,来提高我们程序的执行效率。
网站的加载速度不仅影响着用户体验,也会影响搜索引擎的排名,在百度推出“闪电算法”以来,将网站首屏打开速度被列入优化排名行列,作为前端开发的我们需要如果来优化网站的打开速度呢?下面就整理挖掘出很多细节上可以提升性能的东西分享给大家
DocumentFragments是DOM节点。它们不是主DOM树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到DOM树。在DOM树中,文档片段被其所有的子元素所代替。因为文档片段存在于内存中,并不在DOM树中
对于代码裡面的 if else,我们可以使用逻辑判断式,或更好的三元判断式来优化代码。除了可以降低维护项目的成本之外,还可以提升代码可读性。就让我们从最简单的 if else 例子开始吧。
小程序从发布到现在也已经有将近两年的时间,越来越来多的公司开始重视小程序生态带来的流量,今年也由于小程序平台对外能力的越来越多的开放以及小程序平台的自身优化,越来越多的开发者也自主的投入到小程序的开发当中
无论你正在将 GIF 动图转换为 MP4 视频,还是手头已经有一大堆 MP4 视频,你都可以优化文件结构,以使得这些视频更快地加载和播放。通过重组 atoms 将 moov 放到文件开头,浏览器可以避免发送额外的 HTTP range request 请求来搜寻和定位 moovatom
要优化 Web 服务器的性能,我们先来看看 Web 服务器在 web 页面处理上的步骤:Web 浏览器向一个特定的服务器发出 Web 页面请求; Web 服务器接收到 web 页面请求后,寻找所请求的 web 页面,并将所请求的 Web 页面传送给 Web 浏览器; 显示出来
浏览器下载完页面所有的资源后,就要开始构建DOM树,于此同时还会构建渲染树(Render Tree)。(其实在构建渲染树之前,和DOM树同期会构建Style Tree。DOM树与Style Tree合并为渲染树)
写篇文章的目的,是以开放小程序代码的层面的优化。包括:条件判断将wx:if换成了hidden 、页面跳转请销毁之前使用的资源、列表的局部更新、小程序中多张图片懒加载方案、Input状态下隐藏input,应预留出键盘收起的时间
生活在信息爆炸的今天,我们每天不得不面对和过滤海量的信息--无疑是焦躁和浮动的,这就意味着用户对你站点投入的时间可能是及其吝啬的(当然有一些刚需站点除外)。如何给用户提供迅速的响应就显得十分重要了
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!