JS进阶 - 异步

同步与异步

同步,等待结果

异步,不等待结果

注意,异步和回调往往伴随出现,但是两者没有直接关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 同步的 sleep
function sleep(s){
var start = new Date()
while( new Date() - start < s*1000 ){

}
return
}
console.log(1)
sleep(3)
console.log('wake up')
console.log(2)
// 1 wake up 2

以上是同步的代码。

1
2
3
4
5
6
7
8
// 异步的 sleep
function sleep(s, fn){
setTimeout(fn, s * 1000)
}
console.log(1)
sleep(3, () => console.log('wake up'))
console.log(2)
// 1 2 wake up

console.log() 不会等待上一句执行完,比较 setTimeout 我们可以看到,三秒钟内 JS 什么都不需要做,而同步代码中需要不停计时。

setTimeout 是给浏览器设置的一个闹钟,JS 需要做的只是设置闹钟而不是自己成为闹钟。

经典例子

1
2
document.getElementsByTagName('img')[0].width // 宽度为 0
console.log('done')

第一次加载页面的时候是拿不到图片宽度的,因为 JS 不关心浏览器加载图片的结果,它会在请求过程中获取宽度值,也就是 0.

我们需要改写成加载完成图片时拿到宽高数据:

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

常见的异步,监听事件。

我们再来看一个例子:

1
2
3
4
5
6
let liList = document.querySelectorAll('li')
for(var i=0; i<liList.length; i++){
liList[i].onclick = function(){
conselo.log(i)
}
}

经典的前端小题目,如果给你六个 li,它会一直输出6,因为变量提升,这个 i 会提升到全局当中去,JS 会瞬间执行完整个过程,当你 onclick 的时候,已经晚了。

注意,虽然六项,但是第六项 i=5 的时候仍然会比较并且 +1,所以最终是 6。

浏览器并没有等待代码执行,直接进入下一个循环并得出结果。

把 var i 改成 let 就可以破解:https://zhuanlan.zhihu.com/p/28140450

1
2
3
4
5
6
let liList = document.querySelectorAll('li')
for(let i=0; i<liList.length; i++){
liList[i].onclick = function(){
conselo.log(i)
}
}

AJAX 中的异步

1
2
3
4
5
let request = $.ajax({
url: '.',
async: false
})
console.log(request.responseText)

以上是一个同步的 ajax 代码,ajax 中不应该出现此类代码,因为这个请求会一直占用资源。

我们来改写一个:

1
2
3
4
5
6
7
$.ajax({
url: '.',
async: true,
success: function(responeseText){
conselo.log(responseText)
}
})

这段异步代码告诉浏览器,如果数据传过来了,麻烦你执行一个函数,把内容传给我。

异步的形式

如何拿到异步代码的结果?比如说我们请求一个网络资源,我们使用 ajax,那么怎么获得资源内容呢?

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

  • 傻逼方法:轮询(定期查看结果是否达到)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function buyApple(){
setTimeout(()=>{
window.apple = '买到的苹果'
}, Math.random()*10 *1000)
}

var id = setInterval(()=>{
if(window.apple){
console.log(apple)
window.clearInterval(id)
}else{
console.log('苹果没到')
}
}, 1000)
  • 正规方法:回调(给一个函数)
1
2
3
4
5
6
7
8
9
function buyApple(fn){
setTimeout(()=>{
fn.call(undefined, '苹果')
}, Math.random()*10 *1000)
}

buyApple(function(apple){
console.log(arguments[0])
})

回调的形式

回调得告诉你是成功还是失败,随便你怎么告知。

  1. Node.js 的 error-first 形式:
1
2
3
4
5
6
7
fs.readFile('./1.txt', (error, content)=>{
if(error){
// fail
}esle{
// success
}
})
  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. Promise 的 then 形式
1
2
3
$.ajax({
url: '',
}).then( ()=>{}, ()=>{} ).then( ()=>{} )

我们看到大家对回调报错写法不一,所以我们有了 Promise 规范,你所有的异步操作发出之后,给我暴露一个 API,一个对象,这个对象必须有一个函数,也就是 then。then 必须要有两个回调,成功回调,失败回调。最后那个 then 是对成功操作的加料操作。

如何处理异常?

  1. 如何使用多个 success 函数?
  2. 在有多个成功回调的情况下,如何处理异常?

promise.all()promise.race()

自己返回 Promise

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function buyFruit(){
return new Promise((resolve, reject)=>{
做事
如果成功就谰用resolve
如果失畋就调用reject
})
}

var promise = await ajax()

async functon fn(){
var result = await buyFruit()
return result
}

var r = await fn()
console.log(r)
编辑