1.回调地域问题
异步编程在JavaScript中非常重要,过多的异步编程也带了回调嵌套的问题。
解决方法
(1)拆解function:将各步拆解为单个的function
(2)事件发布/监听模式
一方面,监听某一事件,当事件发生时,进行相应回调操作;另一方面,当某些操作完成后,通过发布事件触发回调,这样就可以将原本捆绑在一起的代码解耦。
(3)Promise
readFile('./sample.txt').then(content => {
let keyword = content.substring(0, 5);
return queryDB(keyword);
}).then(res => {
return getData(res.length);
}).then(data => {
console.log(data);
}).catch(err => {
console.warn(err);
});
(4)generator
generator是es6中的一个新的语法。在function关键字后添加*即可将函数变为generator。
const gen = function* () {
yield 1;
yield 2;
return 3;
}
执行generator将会返回一个遍历器对象,用于遍历generator内部的状态。
let g = gen();
g.next(); // { value: 1, done: false }
g.next(); // { value: 2, done: false }
g.next(); // { value: 3, done: true }
g.next(); // { value: undefined, done: true }
可以看到,generator函数有一个最大的特点,可以在内部执行的过程中交出程序的控制权,yield相当于起到了一个暂停的作用;而当一定情况下,外部又将控制权再移交回来。
(5)async/await
可以看到,上面的方法虽然都在一定程度上解决了异步编程中回调带来的问题。然而:
- function拆分的方式其实仅仅只是拆分代码块,时常会不利于后续维护;
事件发布/监听方式模糊了异步方法之间的流程关系; - Promise虽然使得多个嵌套的异步调用能够通过链式的API进行操作,但是过多的then也增加了代码的冗余,也对阅读代码中各阶段的异步任务产生了一定干扰;
- 通过generator虽然能提供较好的语法结构,但是毕竟generator与yield的语境用在这里多少还有些不太贴切。
因此,这里再介绍一个方法,它就是es7中的async/await。
基本上,任何一个函数都可以成为async函数,以下都是合法的书写形式:
async function foo () {};
const foo = async function () {};
const foo = async () => {};
在async函数中可以使用await语句。await后一般是一个Promise对象。
async function foo () {
console.log('开始');
let res = await post(data);
console.log(`post已完成,结果为:${res}`);
};
当上面的函数执行到await时,可以简单理解为,函数挂起,等待await后的Promise返回,再执行下面的语句。
值得注意的是,这段异步操作的代码,看起来就像是“同步操作”。这就大大方便了异步代码的编写与阅读。
2.Promise
简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,语法上说,Promiese是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。Promise 就是用同步的方式写异步的代码,用来解决回调问题
从控制台打印出来一个Promise