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 对象的 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 的返回值。
- 直接用 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 就是异步的意思, 异步函数也就意味着该函数的执行不会阻塞后面代码的执行。
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 就是异步等待,它等待的是一个 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函数内部代码的阻塞。
任何值得做的事就值得把它做好。- Whatever is worth doing is worth doing well.