异步代码烧脑的原因

同步与异步

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

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

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

1
2
3
4
5
6
7
8
9
10
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+'秒')

异步代码

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

前端经常遇到的异步

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

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

1
2
3
4
5
document.getElementsByTagNames('img')[0].onload = function(){
console.log(this.width) // 宽度不为 0
console.log('real done')
}
console.log('done')

面试题中的异步

为啥点击li每次都打印 6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<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 中的异步

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

1
2
3
4
5
let request = $.ajax({
url: '.',
async: false
})
console.log(request.responseText)
1
2
3
4
5
6
7
8
//异步方式
$.ajax({
url: '/',
async: true,
success: function(responseText){
console.log(responseText)
}
})

异步的形式

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

  1. 傻逼方法:轮训
    老板叫你去买土豆,然后他就隔1分钟问一次买到土豆了吗?
  2. 正规方法:回调
    老板叫你去买土豆并告诉你回来之后通知他,20分钟后你回来了告诉老板是否买到了土豆

回调的形式

  1. Node.js 的 error-first 形式
1
2
3
4
5
6
7
fs.readFile('./1.txt', (error, content)=>{
if(error){
// 失败
}else{
// 成功
}
})
  1. jQuery 的 success / error 形式
1
2
3
4
5
$.ajax({
url:'/xxx',
success:()=>{},
error: ()=>{}
})
  1. jQuery 的 done / fail / always 形式
1
2
3
$.ajax({
url:'/xxx',
}).done( ()=>{} ).fail( ()=>{} ).always( ()=> {})
  1. Prosmise 的 then 形式
1
2
3
4
//成功调第一个函数,失败调用第二个函数
$.ajax({
url:'/xxx',
}).then( ()=>{}, ()=>{} ).then( ()=>{})
  • ajax的 Prosmise跟真正的Promise有点不同
  • 它也可以一直.then().then().then() 这样链式操作

Promise只是一种回调的形式

Promise规范

1
2
3
4
5
axios({
url:'xxx'
}).then(s1,e1)
.then(s2,e2)
.then(s3,e3)

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

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

Promise规范是责任制

1
2
3
s1 e1 是第一责任人
s2 e2 是第二责任人
s3 e3 是第三责任人
1
2
3
4
5
6
7
8
9
10
11
12
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 就会报错到浏览器显示给开发者

帮你兜底的两种写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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)}
)
1
2
3
4
5
6
7
8
9
10
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

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

分析2

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