异步操作和async函数

异步编程对于JavaScript是极为重要的。JavaScript只有一个线程,如果异步编程,得卡死,基本没法用。

ES6诞生之前,异步编程的方法大概有以下几种:

  • 回调函数
  • 事件监听
  • 发布/订阅
  • Promise对象

ES7中的async函数更是给出异步编程的终极解决方案

基本概念

异步

所谓“异步”,简单说就是一个任务分成两段执行,先执行第一段,然后转而执行其他任务,等做完准备工作再回过头执行第二段。

相应的,连续执行的就叫同步。

回调

所谓“回调”就是把任务的第二段单独写在一个函数中,等到重新执行该任务时直接调用这个函数。

读取文件进行处理是这样写的:

1
2
3
4
fs.readFile('/etc/passwd', function(err, data) {
if (err) throw err
console.log(data)
})

为什么nodejs约定回调函数的第一个参数必须是错误对象err(如果没有错误,该参数就是null)?原因是执行拆分成两段,在这两段之间抛出的错误程序无法捕获,只能当做参数传入第二段

Promise

当回调函数过多,多重嵌套。代码不是纵向发展而是横向发展,很快代码就会乱成一团。Promise就是为了解决这个问题诞生的。

Promise不是新的语法功能,而是一种新的写法,允许将回调函数的横向加载改成纵向加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var readFile = require('fs-readfile-promise')

readFile(fileA)
.then(function(data){
console.log(data.toString())
})
.then(function(){
return readFile(fileB)
})
.then(function(){
console.log(data.toString())
})
.catch(function(err){
console.log(err)
})

Promise最大的问题是代码冗余,原来的任务被Promise包装了一下,不管什么操作,一眼看去都是一堆then,原来的语义并不清楚。

Thunk函数

传名调用和传值调用

  • 传值调用:在进入函数之前,就计算参数的值
1
2
3
4
5
var x = 1

f(x + 5)
//传值调用等同于
f(6)
  • 传名调用:传值调用比较简单,但是对参数求值时并未使用这个参数,有可能造成性能损失。而传名调用就是只在执行时求值

Thunk函数的含义

编译器“传名调用”实现往往是先将参数放到一个临时函数中,在将这个临时函数传入函数体。这个临时函数就叫Thunk

1
2
3
4
5
6
7
8
9
10
11
12
13
function f(m) {
return m * 2
}

f (x + 5)
//等同于 参数放到一个临时函数
var thunk = function() {
return x + 5
}

function f(thunk) {
return thunk() * 2
}

async函数

async函数返回值是Promise,你可用then方法指定下一步操作。

async函数实现

async函数的实现就是将Generator函数和自动执行器包装在一个函数中

1
2
3
4
5
6
7
8
async function fn(args) {
//参数运算
}

//等同于
function fn(args) {
return spawn(function *)
}

asycn的用法

同Generator函数一样,async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行时,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体内后台的语句

1
2
3
4
5
6
7
8
9
async function getStokPriceByName(name) {
var symbo = await getStockSymbol(name)
var stockPrice = await getStockPrice(symbol)
return stockPrice
}

getStockPriceByName('goog').then(function(result){
console.log(result)
})

上面的代码是一个获取股票报价的函数,函数前面的async关键字表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。

下面这个例子指定了多少毫秒后输出一个值。

1
2
3
4
5
6
7
8
9
10
11
12
function timeout((resolve) => {
setTimeout(resolve, ms)
})

async function asyncPrint(value, ms) {
await timeout(ms)
console.log(valuse)
}

asyncPrint("Hello World!", 50)

//50毫秒后输出, "Hello World!"

注意点

await命令后面的Promise对象,运行结果可能是Rejected,所以最好把await命令放在try…catch代码块中

1
2
3
4
5
6
7
8
9
async function myFunction() {
try {
await(
await somethingThatReturnAPromise()
)catch (err) {
console.log(err)
}
}
}