前端异步代码烧脑的原因

Posted yangsg

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端异步代码烧脑的原因相关的知识,希望对你有一定的参考价值。

同步与异步

同步:等待结果
异步:不等待结果

注意,异步常常伴随回调一起出现,但是异步不是回调,回调也不一定是异步

如下代码就是同步,只要fn内部的循环完毕才能执行下面的代码

function fn(){
    for(var i=0;i<5000000000;i++){
        
    }
}
var startTime = new Date().getTime();
console.log(‘开始了‘)
fn();
var exeTime = new Date().getTime() - startTime;
console.log(‘执行了‘+exeTime/1000+‘秒‘)

  

异步代码

  • 定时器就相当于浏览器设置了一个闹钟
  • 之后继续执行
  • 闹钟到了时间再去执行相应的代码
//异步的 asyncFn
function asyncFn(fn){
    setTimeout(fn, 3000)
}
console.log(1)
asyncFn(()=> console.log(‘wake up‘))
console.log(2)

前端经常遇到的异步

//因为图片还没有加载完毕所以并不知道高度,(请禁用浏览器缓存在执行)
document.getElementsByTagNames(‘img‘)[0].width // 宽度为 0
console.log(‘done‘)

你应该在图片加载成功后在获取图片的高度

document.getElementsByTagNames(‘img‘)[0].onload = function(){
    console.log(this.width) // 宽度不为 0
    console.log(‘real done‘)
}
console.log(‘done‘)

面试题中的异步

为啥点击li每次都打印 6

<li>11111111</li>
<li>22222222</li>
<li>33333333</li>
<li>44444444</li>
<li>55555555</li>
<li>66666666</li>

......

let liList = document.querySelectorAll(‘li‘)
for(var i=0; i<liList.length; i++){
    liList[i].onclick = function(){
        console.log(i)
    }
}
console.log(i)  //6
  1. for循环用极短的时间执行完毕 我们假设 3 毫秒
  2. var i 有变量提升 即使循环之后仍然可以访问到i的值
  3. 用户看到 Li 的时间绝对大于3 毫秒,每个 Li 点击事件已经初始化结束
  4. log(i) 还是外面的 i

如何解决 把 var i 改为 let i 就可以了

详情参考

AJAX 中的异步

这样调用虽然是同步的,但是页面会失去响应,直到结果回来

let request = $.ajax({
  url: ‘.‘,
  async: false
})
console.log(request.responseText)
//异步方式
$.ajax({
    url: ‘/‘,
    async: true,
    success: function(responseText){
        console.log(responseText)
    }
})

  

异步的形式

一般有两种方式拿到异步结果

傻逼方法:轮训
老板叫你去买土豆,然后他就隔1分钟问一次买到土豆了吗?

正规方法:回调
老板叫你去买土豆并告诉你回来之后通知他,20分钟后你回来了告诉老板是否买到了土豆

回调的形式

 

Node.js 的 error-first 形式

fs.readFile(‘./1.txt‘, (error, content)=>{
    if(error){
        // 失败
    }else{
        // 成功
    }
})

 

jQuery 的 success / error 形式

$.ajax({
    url:‘/xxx‘,
    success:()=>{},
    error: ()=>{}
})

 

jQuery 的 done / fail / always 形式

$.ajax({
    url:‘/xxx‘,
}).done( ()=>{} ).fail( ()=>{} ).always( ()=> {})

 

Prosmise 的 then 形式

//成功调第一个函数,失败调用第二个函数
$.ajax({
    url:‘/xxx‘,
}).then( ()=>{}, ()=>{} ).then( ()=>{})
  • ajax的 Prosmise跟真正的Promise有点不同
  • 它也可以一直.then().then().then() 这样链式操作

Promise只是一种回调的形式

Promise规范

axios({
    url:‘xxx‘
}).then(s1,e1)
  .then(s2,e2)
  .then(s3,e3)

如果是jquery,成功只会走进成功的分支

s1 ==> s2 ==> s3 
e1 ==> e2 ==> e3

Promise规范是责任制

s1 e1 是第一责任人
s2 e2 是第二责任人
s3 e3 是第三责任人
axios({
    url:‘/aaa‘
}).then(()=>{
    console.log(‘成功1‘)
    alert(xxxxx)  //xxxxx 没有定义
},()=>{
    console.log(‘失败1‘)        
}).then(()=>{
    console.log(‘成功2‘)
},()=>{
    console.log(‘失败2‘)
})
  1. s1 执行过程不够顺利报错了(就会产生错误的结果)
  2. 于是第二责任人接收到的是一个烂尾工程
  3. 当做错误处理 走进e2

如果没有 e2 就会报错到浏览器显示给开发者

两种写法
axios({
    url:‘/aaa‘
}).then(()=>{
    console.log(‘成功1‘)
    alert(xxxxx)  //xxxxx 没有定义
},()=>{
    console.log(‘失败1‘)        
}).then(()=>{
    console.log(‘成功2‘)
}).catch((err)=>{
    console.log(err)
})


axios({
    url:‘/aaa‘
}).then(()=>{
    console.log(‘成功1‘)
    alert(xxxxx)  //xxxxx 没有定义
},()=>{
    console.log(‘失败1‘)        
}).then(()=>{
    console.log(‘成功2‘)
}).then(undefined,(err)=>{
    console.log(err)
})

自己返回 Promise

function buyApple(){
    var fn =(succ,err)=>{
        var rnd = Math.random()
        setTimeout(()=>{
            if(rnd>0.5){
                succ(‘买到了苹果‘)
            }else{
                err(‘没买到苹果‘)
            }
        },2000)
    }
    return new Promise(fn)   //fn.call(undefined,success,err)
}


var promise = buyApple();
promise.then(
    (data)=>{console.log(data)},
    (err)=>{console.log(err)}
)
function ajax(){
    return new Promise((resolve, reject)=>{
        做事
        如果成功就调用 resolve
        如果失败就调用 reject
    })
}

var promise = ajax()
promise.then(successFn, errorFn)

Promise 深入阅读:http://www.cnblogs.com/hustskyking/p/promise.html
Promise/A+ 规范:https://segmentfault.com/a/1190000002452115

async / await

await
function buyApple(){
    var fn =(succ,err)=>{
        var rnd = Math.random()
        setTimeout(()=>{
            if(rnd>0.5){
                succ(‘买到了苹果‘)
            }else{
                err(‘没买到苹果‘)
            }
        },10000)
    }
    return new Promise(fn)   //fn.call(undefined,success,err)
}

var result = await buyApple();
console.log(2)

10秒之后返回买苹果的结果(成功/失败)
打印2

  • await相当于 用异步的方式写同步的代码
  • 直到异步代码结果回来才把值赋给 result

await的用法就是 后面接一个返回Promise对象的函数

async
function buyApple(){
    var fn =(succ,err)=>{
        var rnd = Math.random()
        setTimeout(()=>{
            if(rnd>0.5){
                succ(‘买到了苹果‘)
            }else{
                err(‘没买到苹果‘)
            }
        },10000)
    }
    return new Promise(fn)   //fn.call(undefined,success,err)
}

async function fn(){
    var result = await buyApple()
    return result
}

var r = await fn().then(
                    (data)=>{console.log(data)},
                    (err)=>{console.log(err)})
console.log(1)

分析1

var r = await fn().then(
                    (data)=>{console.log(data)},
                    (err)=>{console.log(err)})
console.log(2)
/*
1.会直接打印2
2. 10秒后打印  买苹果的结果
*/

分析2

var r = await fn().then(
                    (data)=>{console.log(data)},
                    (err)=>{console.log(err)})
console.log(2)
/*
10秒后打印  买苹果的结果
然后打印2
*/

 

以上是关于前端异步代码烧脑的原因的主要内容,如果未能解决你的问题,请参考以下文章

排序(令我烧脑的东西,跪着也要理解)

tableau一道烧脑的表计算面试题

排序(令我烧脑的东西,跪着也要理解)

用AIOps做根因分析?先搞懂这个烧脑的统计学悖论

C语言多层递归函数(最烧脑的一种递归)

NFT,今年最烧脑的财富大冒险:有人赚翻 有人亏损过半