概念
异步(Asynchronous, async)是与同步(Synchronous, sync)相对的概念。
在我们学习的传统单线程编程中,程序的运行是同步的(同步不意味着所有步骤同时运行,而是指步骤在一个控制流序列中按顺序执行)。而异步的概念则是不保证同步的概念,也就是说,一个异步过程的执行将不再与原有的序列有顺序关系。
简单来理解就是:同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。
How
JavaScript中的异步机制可以分为以下几种:
回调函数 的方式,使用回调函数的方式有一个缺点是,多个回调函数嵌套的时候会造成回调函数地狱,上下两层的回调函数间的代码耦合度太高,不利于代码的可维护。
代码的耦合度,是指代码中的单元代码的紧密程度,其中一个单元代码的更改对其它单元代码的影响力与作用。 代码间的耦合度越高,系统就在变动时就更加难以控制,但并非不能控制,只是你将为此付出巨大的代价。 软件的设计,不仅是理清思路,更多的意义是将软件中的逻辑结构进行合理地描述,力图减少各单元代码间的影响力,使得系统在控制上更加容易,减少出错的机会。
Promise 的方式,使用 Promise 的方式可以将嵌套的回调函数作为链式调用。但是使用这种方法,有时会造成多个 then 的链式调用,可能会造成代码的语义不够明确。
- generator 的方式,它可以在函数的执行过程中,将函数的执行权转移出去,在函数外部还可以将执行权转移回来。当遇到异步函数执行的时候,将函数执行权转移出去,当异步函数执行完毕时再将执行权给转移回来。因此在 generator 内部对于异步操作的方式,可以以同步的顺序来书写。使用这种方式需要考虑的问题是何时将函数的控制权转移回来,因此需要有一个自动执行 generator 的机制,比如说 co 模块等方式来实现 generator 的自动执行。
- async 函数 的方式,async 函数是 generator 和 promise 实现的一个自动执行的语法糖,它内部自带执行器,当函数内部执行到一个 await 语句的时候,如果语句返回一个 promise 对象,那么函数将会等待 promise 对象的状态变为 resolve 后再继续向下执行。因此可以将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动执行。
语法糖:用更简练的言语表达较复杂的含义。在得到广泛接受的情况之下,可以提升交流的效率。
When
在前端编程中(甚至后端有时也是这样),我们在处理一些简短、快速的操作时,例如计算 1 + 1 的结果,往往在主线程中就可以完成。主线程作为一个线程,不能够同时接受多方面的请求。所以,当一个事件没有结束时,界面将无法处理其他请求。
现在有一个按钮,如果我们设置它的 onclick 事件为一个死循环,那么当这个按钮按下,整个网页将失去响应。
为了避免这种情况的发生,我们常常用子线程来完成一些可能消耗时间足够长以至于被用户察觉的事情,比如读取一个大文件或者发出一个网络请求。因为子线程独立于主线程,所以即使出现阻塞也不会影响主线程的运行。但是子线程有一个局限:一旦发射了以后就会与主线程失去同步,我们无法确定它的结束,如果结束之后需要处理一些事情,比如处理来自服务器的信息,我们是无法将它合并到主线程中去的。
Promise
Promise 是一个 ECMAScript 6 提供的类,目的是更加优雅地书写复杂的异步任务。
// 例如,如果我想分三次输出字符串,// 第一次间隔 1 秒,第二次间隔 4 秒,第三次间隔 3 秒:setTimeout(function () {console.log("First");setTimeout(function () {console.log("Second");setTimeout(function () {console.log("Third");}, 3000);}, 4000);}, 1000);// 这段程序实现了这个功能,但是它是用 "函数瀑布" 来实现的。// 可想而知,在一个复杂的程序当中,用 "函数瀑布" 实现的程序无论是维护还是异常处理都是一件特别繁琐的事情,// 而且会让缩进格式变得非常冗赘。// 现在我们用 Promise 来实现同样的功能:new Promise(function (resolve, reject) {setTimeout(function () {console.log("First");resolve();}, 1000);}).then(function () {return new Promise(function (resolve, reject) {setTimeout(function () {console.log("Second");resolve();}, 4000);});}).then(function () {setTimeout(function () {console.log("Third");}, 3000);});
使用
Promise 构造函数只有一个参数,是一个函数,这个函数在构造之后会直接被异步运行,所以我们称之为起始函数。起始函数包含两个参数 resolve 和 reject。
当 Promise 被构造时,起始函数会被异步执行:
new Promise(function (resolve, reject) {console.log("Run");});
这段程序会直接输出 Run。
resolve 和 reject 都是函数,其中调用 resolve 代表一切正常,reject 是出现异常时所调用的:
new Promise(function (resolve, reject) {var a = 0;var b = 1;if (b == 0) reject("Divide zero");else resolve(a / b);}).then(function (value) {console.log("a / b = " + value);}).catch(function (err) {console.log(err);}).finally(function () {console.log("End");});
