JavaScript异步代码优化

callback

1
2
3
4
传递函数作为回调
function(ag1,ag2...,callback) {
  // 业务逻辑代码
}

callback 顾名思义便是回调,但并不是将回调内容放在异步方法里,而是放到外部的回调函数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
window.mytest = function(str, callback) {
printStr(str) ;
var res = test_callback();
callback(res);
}

function printStr(str) {
alert(str);
}

function test_callback() {
return "回调测试";
}

如此我们看似异步的代码变成了同步的写法,避免了层层嵌套的写法,看上去也流畅了很多。同时使用 callback 也是异步编程最基础和核心的一种解决思路。

Promise

基于 callback,Promise 目前也被广泛运用,其是异步编程的一种解决方案,比传统的回调函数解决方案更合理和强大。

1
2
3
4
5
new Promise(test).then(function (result) {
console.log('成功:' + result);
}).catch(function (reason) {
console.log('失败:' + reason);
});

当然如果要等待多个异步请求完成执行某些操作,可以使用 Promise.all 方法,如:

1
2
3
4
5
let p = Promise.all([p1, p2, p3]); // 其中p1、p2、p3都是Promise实例
p.then(result => console.log(result));

// 或者
p1.then(p2).then(p3).catch(handleError); // 其中p1、p2、p3都是Promise实例

当然 Promise 也有其相应的缺点,比如下一个 then 回调只能获取上一个 then 返回的数据,不能跨层获取,同时大量的 then 回调也会使代码不容易维护。

Generator

generator 跟函数很像,定义如下:

1
2
3
4
5
6
7
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}

// generator和函数不同的是,generator由function*定义(注意多出的*号),并且,除了return语句,还可以用yield返回多次。

要编写一个产生斐波那契数列的函数,可以这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
function* fib(max) {
var
t,
a = 0,
b = 1,
n = 0;
while (n < max) {
yield a;
[a, b] = [b, a + b];
n ++;
}
return;
}
  • 调用 generator 对象有两个方法
  1. 不断地调用 generator 对象的 next()方法:
1
2
3
4
5
6
7
var f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // {value: undefined, done: true}

next()方法会执行 generator 的代码,然后,每次遇到 yield x;就返回一个对象{value: x, done: true/false},然后“暂停”。返回的 value 就是 yield 的返回值,done 表示这个 generator 是否已经执行结束了。如果 done 为 true,则 value 就是 return 的返回值。

  1. 直接用 for … of 循环迭代 generator 对象,这种方式不需要我们自己判断 done:
1
2
3
for (var x of fib(10)) {
console.log(x); // 依次输出0, 1, 1, 2, 3, ...
}
  • 缺点:Generator 函数的缺点在于,我们每一次执行 yield 语句都需要手动进行 next,不是很方便。
  • 优点:把异步回调代码变成“同步”代码。
1
2
3
4
5
6
7
8
9
try {
r1 = yield ajax('http://url-1', data1);
r2 = yield ajax('http://url-2', data2);
r3 = yield ajax('http://url-3', data3);
success(r3);
}
catch (err) {
handle(err);
}

async and await

ES7 还提供了更加方便的 async 函数和 await 命令,其实 async 是 Generator 函数的语法糖,不同点在于其内置了执行器,也就是说 async 函数自带执行器。

  • async 的用法

它作为一个关键字放到函数前面,用于表示函数是一个异步函数,因为 async 就是异步的意思, 异步函数也就意味着该函数的执行不会阻塞后面代码的执行。

1
2
3
4
5
6
7
8
9
10
async function timeout() {
 return 'hello world';
}

// async 函数返回的是一个promise 对象,如果要获取到promise 返回值,我们应该用then 方法。

timeout().then(result => {
console.log(result);
})
console.log('虽然在后面,但是我先执行');
  • await 关键字

顾名思义, await 就是异步等待,它等待的是一个 Promise,因此 await 后面应该写一个 Promise 对象,如果不是 Promise 对象,那么会被转成一个立即 resolve 的 Promise。注意 await 关键字只能放到 async 函数里面。

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
async function sayHi_async(name) {
try{

const sayHi_1 = await sayHi(name)
console.log(`你好, ${sayHi_1}`)

const sayHi_2 = await sayHi('李四')
console.log(`你好, ${sayHi_2}`)

const sayHi_3 = await sayHi('王二麻子')
console.log(`你好, ${sayHi_3}`)

} catch (err){

console.log(err)

}
}

// 执行
sayHi_async('张三')

// 结果
console.log("你好, 张三")
console.log("你好, 李四")
console.log("你好, 王二麻子")

async函数调用不会造成代码的阻塞,但是await会引起async函数内部代码的阻塞。