一、什么是异步

1. JS为何会有异步?

JS是单线程语言。例如如下示例,单线程就是程序一行行执行,如果上面一行没执行完,那么就一直等执行完成后才去继续执行下一行。

1
2
3
4
5
var i, t = Date.now()
for (i = 0; i < 100000000; i++) {
}

console.log(Date.now() - t) // 250 (chrome浏览器)

上述情况勉强还可接受,但是对于请求网络资源,接口未返回数据时就一直傻傻等着那肯定不行。因此对于这种场景,JS就设计了异步。

2. 异步的实现原理?

1
2
3
4
5
6
var ajax = $.ajax({
url: '/data/data1.json',
success: function () {
console.log('success')
}
})
1
2
3
4
var fs = require('fs')
fs.readFile('data1.json', (err, data) => {
console.log(data.toString())
})

从上面两个 demo 看来,实现异步的最核心原理,就是将callback作为参数传递给异步执行函数,当有结果返回之后再触发 callback执行,就是如此简单!

3. 常用的异步操作?

(1)网络请求

(2)I/O操作

(3)定时函数:setTimeout、setInterval

二、异步和EventLoop

三、JS异步操作

1. 传统的$.ajax:

1
2
3
4
5
6
7
8
9
10
11
12
// 1. 使用 success() 和 error() 回调函数
var ajax = $.ajax({
url: 'data.json',
success: function () {
console.log('success')
},
error: function () {
console.log('error')
}
})

console.log(ajax) // 2. 返回一个 XHR 对象

2. 1.5版本后的$.ajax:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1. 可以链式的执行 done() 或 fail()
var ajax = $.ajax('data.json')
ajax.done(function () {
console.log('success 1')
})
.fail(function () {
console.log('error')
})
.done(function () {
console.log('success 2')
})


console.log(ajax) // 2. 返回一个 deferred 对象

(1)改进之后的好处:

  • 1.5版本前的ajax和1.5之后的ajax的主要区别是$.ajax()返回值不同,1.5前是返回一个xhr,1.5之后是返回deferred对象,而这个deferred对象带有done() 和 fail()方法,而这两个方法封装了回调,都是在请求结果后才去自动调用的。这种方式为之后Promise标准的制定提供了很大的参考意义,deferred可以认为是Promise的前身。
  • 虽然 JS 是异步执行的语言,但是人的思维是同步的。因此,开发者总是在寻求如何使用逻辑上看似同步的代码来完成 JS 的异步请求。而 jquery 的这一次更新,让开发者在一定程度上得到了这样的好处。
  • 之前无论是什么操作,我都需要一股脑写到callback中,现在不用了。现在成功了就写到done中,失败了就写到fail中,如果成功了有多个步骤的操作,那我就写很多个done,然后链式连接起来就 OK 了。

(2)jquery实现异步的原理:

  • jquery 通过deferred对象解决了异步中callback函数的问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function waitHandle() {
    var dtd = $.Deferred() // $.Deferred() 会返回一个 deferred 对象。deferred对象有done、fail、then方法,还有resolve、reject方法,前三个方法是状态变化之后才会触发的监听函数,后两个方法是主动触发用来改变状态。
    var wait = function (dtd) { // 要求传入一个 deferred 对象
    var task = function () {
    console.log('执行完成')
    dtd.resolve() // 表示异步任务已经完成
    }
    setTimeout(task, 2000)
    return dtd // 要求返回 deferred 对象,再次返回deferred对象的目的是可以链式调用
    }

    // 注意,这里一定要有返回值
    return wait(dtd)
    }
  • 由于deferred有五个方法,但其中的resolve和reject只能是主动触发来修改状态。但是上述程序最终返回的结果是deferred对象,这样外部就可以调用resolve和reject,而产生意想不到的后果。所以我们优化一下:

这边利用deferred对象的promise对象没有resolve和reject方法,而只有done、fail、then。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function waitHandle() {
var dtd = $.Deferred()
var wait = function (dtd) {
var task = function () {
console.log('执行完成')
dtd.resolve()
}
setTimeout(task, 2000)
return dtd.promise() // 注意,这里返回的是 primise 而不是直接返回 deferred 对象
}
return wait(dtd)
}

var w = waitHandle() // 经过上面的改动,w 接收的就是一个 promise 对象。这样的话外部就不能调用resolve或者reject方法
$.when(w)
.then(function () {
console.log('ok 1')
})
.then(function () {
console.log('ok 2')
})

3. Promise 实现异步

promise的出现离不开jquery中ajax的实现思想,所以上述了解ajax1.5前后的版本,及实现原理都是为了更好的理解Promise。

(详情请看下节:深入理解JavaScript异步(二、Promise))

4. ES6中的Generator

(详情请看下节:深入理解JavaScript异步(三、generator))

5. ES7中的async-await:Generator的语法糖

(详情请看下节:深入理解JavaScript异步(三、Async/Await))

参考:

深入浅出JavaScript异步编程

深入理解 JavaScript 异步