【学习笔记】JS经典异步操作,从闭包到async/await
参考文献:王仕军——知乎专栏前端周刊
感谢作者的热心总结,本文在理解的基础上,根据自己能力水平作了一点小小的修改,在加深自己印象的同时也希望能和各位共同进步...
1. 异步与for循环
抛出一个问题,下面的代码输出什么?
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
6 console.log(i);
相信绝大部分同学都能答的上,它的正确答案是立即输出5,过1秒钟后一次性输出5个5,这是一个典型的JS异步问题,首先for循环的循环体是一个异步函数,并且变量i添加到全局环境中,所以立即输出一个5,一秒钟后,异步函数setTimeout输出五次循环的结果,打印5 5 5 5 5(没有时间间隔)。
2. 闭包
现在我们把需求改一下,希望输出的结果是5 ->0,1,2,3,4, 应该怎么修改代码呢?
很明显我们可以用闭包创建一个不销毁的作用域,保证变量i每次都能正常输出。
for(var i=0;i<5;i++){
(function(j)
{setTimeout(() => {
console.log(j); //过一秒输出 0,1,2,3,4
}, 1000)})(i)
}
console.log(i); //立即输出5
因为立即执行会造成内存泄漏不建立大量使用,那么我们还可以这样
var output = function(i){
setTimeout(()=>{
console.log(i); // 过1秒输出0,1,2,3,4
},1000)
}
for(var i=0;i<5;i++){
output(i);
}
console.log(i); //立即输出5
JS基本类型是按值传递的,我们给函数output传了一个参数,所以它就会保存每次循环的实参,所以得到的结果和采用立即执行函数的结果一致。
3. ES6语法
当然我们也可以使用ES6的语法,还记得for循环中使用let声明可以有效阻止变量添加到全局作用域吗?
for(let i=0;i<5;i++){
setTimeout(()=>{
console.log(i) //一秒钟后同时输出0,1,2,3,4
},1000)
}
6 console.log(i) //这一行会报错,因为i只存在于for循环中
for循环中let声明有一个特点,i只在本轮循环中有效,所以每循环一个i其实都是新变量,而javaScript引擎内部会记住上一次循环的值,初始化变量i时,就在上轮循环基础上计算。
现在我们又改一下需求,希望先输出0,之后每隔一秒依次输出1,2,3,4,循环结束再输出5。
很容易想到,我们可以再增加一个定时器,定时器的时间和循环次数有关
for(var i=0;i<5;i++){
(function(j){
setTimeout(() => {
console.log(j) //立即输出0,之后每隔1秒输出1,2,3,4
}, 1000*j);
})(i)
}
setTimeout(()=>{
console.log(i) //循环结束输出5
},1000*i)
这虽然也是个办法,但代码写着确实不太好看,异步操作我们首先就要想到Promise对象,尝试用Promise对象来改写
let tasks = [];
for(var i=0;i<5;i++){
((j)=>{
tasks.push(new Promise(
(resolve)=>{
setTimeout(() => {
console.log(j);
resolve(); //执行resolve,返回Promise处理结果
}, 1000*j);
}
))
})(i)
}
Promise.all(tasks).then(()=>{
setTimeout(() => {
console.log(i);
}, 1000); //只要把时间设为1秒
})
Promise.all返回一个Promise实例,在tasks的promise状态为resolved时回调完成,这就是我们必须要在循环体中resolve()的原因。
我们将上面的代码重新排版,让其颗粒度更小,模块化更好,简洁明了
let tasks = []; //存放一个异步操作
let output = (i)=> //返回一个Promise对象
new Promise((resolve)=>{
setTimeout(() => {
console.log(i);
resolve();
}, 1000*i);
})
for(var i=0;i<5;i++){ //生成全部的异步操作
tasks.push(output(i))
}
Promise.all(tasks).then(()=>{ //tasks里的promise对象都为resolved调用then链的第一个回调函数
setTimeout(() => {
console.log(i)
}, 1000);
})
4. async/await优化
上次写了一篇关于async和await优化then链的博客,感兴趣的可以看看:深入理解async/await
对于then链,我们是可以进一步优化的:
let sleep = (timeountMS) => new Promise((resolve) => {
setTimeout(resolve, timeountMS);
}); (async () => { // 声明即执行的 async 函数表达式
for (var i = 0; i < 5; i++) {
await sleep(1000);
console.log(i);
}
await sleep(1000);
console.log(i);
})();
【学习笔记】JS经典异步操作,从闭包到async/await的更多相关文章
- js经典试题之闭包
js经典试题之闭包 1:以下代码输出的结果是? function Foo(){ var i=0; return function(){ document.write(i++); } } var f1= ...
- amazeui学习笔记--js插件(UI增强)--警告框Alert
amazeui学习笔记--js插件(UI增强)--警告框Alert 一.总结 1.警告框基本样式:用am-alert声明div容器, <div class="am-alert" ...
- amazeui学习笔记--js插件(UI增强4)--下拉组件Dropdown
amazeui学习笔记--js插件(UI增强4)--下拉组件Dropdown 一.总结 1.am-dropdown(及其孩子):控制下拉列表的样式 2.data-am-dropdown(及其孩子):控 ...
- amazeui学习笔记--js插件(UI增强3)--折叠面板Collapse
amazeui学习笔记--js插件(UI增强3)--折叠面板Collapse 一.总结 注意点: 1.data-am-collapse:这个东西就是展开折叠事件 2.am-collapse(包括其下属 ...
- amazeui学习笔记--js插件(UI增强2)--按钮交互Button
amazeui学习笔记--js插件(UI增强2)--按钮交互Button 一.总结 1.按钮loading状态: <button type="button" class=&q ...
- 前端学习:学习笔记(JS部分)
前端学习:学习笔记(JS部分) 前端学习:JS学习总结(图解) JS的简介 JS基本语法 JS内置对象 JS的函数 JS的事件 JS的BOM JS的DOM JS的简介 新建步骤 <body ...
- JavaScript学习笔记——JS中的变量复制、参数传递和作用域链
今天在看书的过程中,又发现了自己目前对Javascript存在的一个知识模糊点:JS的作用域链,所以就通过查资料看书对作用域链相关的内容进行了学习.今天学习笔记主要有这样几个关键字:变量.参数传递.执 ...
- [学习笔记]JS 数组Array push相关问题
前言: 今天用写了一个二维数组,都赋值为零,然后更新其中一个值,结果和预期是不一样,会整列的相同位置都是同一个值. 1.用Chrome的控制台样例如下: arrs[2][2] =1的赋值,竟然是三个数 ...
- js异步编程终级解决方案 async/await
在最新的ES7(ES2017)中提出的前端异步特性:async.await. async.await是什么 async顾名思义是“异步”的意思,async用于声明一个函数是异步的.而await从字 ...
随机推荐
- trinitycore 魔兽服务器源码分析(三) 多线程相关
先看LockedQueue.h template <class T, typename StorageType = std::deque<T> >class LockedQue ...
- 《C#从现象到本质》读书笔记(六)第8章委托和事件
<C#从现象到本质>读书笔记(六)第二部分 C#特性 第8章委托和事件 从这一部分开始,知识点就相对少了,重要的是代码练习.奈何太高深的代码平常不怎么用,这些特性也不是经常写代码的. 委托 ...
- 【NIFI】 Apache NiFI 之 ExecuteScript处理(二)
本例介绍NiFI ExecuteScript处理器的使用,使用的脚本引擎ECMScript 接上一篇[NIFI] Apache NiFI 之 ExecuteScript处理(一) ExecuteScr ...
- MFC中List控件动态填充数据(LVN_GETDISPINFO)
在使用List控件的过程中,有时候List控件中需要添加大量的数据,如果使用InsertItem填充,会一次性将数据全部添加进List控件中,比较耗时.这里记录下如何动态添加List控件数据. 步骤 ...
- spring+shiro+ehcache整合
1.导入jar包(pom.xml文件) <!-- ehcache缓存框架 --> <dependency> <groupId>net.sf.ehcache</ ...
- stm32手册上的英文
crystal-less 无晶振 USB FS(Full-speed)此外还有High-speed接口(简称HS),Low-speed接口(简称LS) frequency频率 CRC(Cyclic ...
- noip第20课资料
- Android-Java-饿汉式单例模式(内存图)
描述Single对象: package android.java.oop14; public class Single { // 默认构造方法 私有化 不让外界调用 private Single() ...
- 常用string格式化
1.格式化货币(跟系统的环境有关,中文系统默认格式化人民币,英文系统格式化美元) string.Format("{0:C}",0.2) 结果为:¥0.20 (英文操作系统结果:$0 ...
- CPU Hardwar
GPU负责把线程块分配到各个SM上处理. CUDA对申请的线程块何时运行,以及在哪个SM上运行是没有保证的.这恰好是GPU的优势,这种方式带来了灵活性,不需程序根据SM的数量去配置程序. 但是一个bl ...