js 异步编程的三种模式
写在前面
javascript 语言的执行环境是"单线程"(single thread),就是指一次只能完成一件任务。如果有多个任务,就必须排队,等前面一个任务完成,再执行后面一个任务,以此类推。
这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。
单线程
很容易可以看出,上述代码会依次输出 1,2。因为代码是从上到下,依次执行,执行完 f1(),才会执行 f2()。但是如果 f1()中的代码执行的是读取文件或者 ajax 操作呢,文件的读取都需要一定时间,难道我们需要完全等到文件完全读完再进行写操作么?为了解决这个问题,接下来我们来探究一下 js 中 同步和异步 的概念。
同步和异步
同步
指在 主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。
也就是调用一旦开始,必须这个调用 返回结果(划重点——)才能继续往后执行。程序的执行顺序和任务排列顺序是一致的。
异步
异步任务是指不进入主线程,而进入 任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。
每一个任务有一个或多个 回调函数。前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行。
程序的执行顺序和任务的排列顺序是不一致的,异步的。
我们常用的 setTimeout 和 setInterval 函数,Ajax 都是异步操作。
那么如何实现异步编程呢,笔者介绍几种方法
回调函数(Callback)
回调函数,这是异步编程最基本的方法。
上述代码通过回调函数的嵌套,从文件系统中读取一个./pakage.json 文件并写入./p.json,读取成功两秒后输出'ok'。用回调来实现异步,没有什么问题。
但是试想,如果再多几个异步函数,代码整体的维护性,可读性都变的极差,如果出了 bug,修复过程也变的极为困难,这个便是所谓的 回调函数地狱。
Promise 对象
Promise 对象用于表示一个异步操作的最终状态(完成或失败),以及其返回的值。
MDN 对 Promise 定义如上,Promise 本意为承诺,我们可以理解为程序承诺过一段时间后会给你一个结果。Promise 是一个对象,可以保存三个状态 每一时刻必须有一个状态。
成功 Fulfilled
失败 Rejected
处理中 Pending
默认 pending 如果调用 resolve fulfilled
默认 pending 如果调用 reject rejeced
这么一看,并没有什么区别,还比上面的异步回调复杂,得先新建 Promise 再定义其回调。但其实,Promise 的真正强大之处在于它的多重链式调用,可以避免层层嵌套回调。
我们先使用new
来构建一个promise
。Promise 接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。
resolve
:成功时调用,并将结果,作为参数传递出去;
reject
:失败时调用,并将错误,作为参数抛出。
then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个 参数是 Promise 执行失败时的回调。
Promise 对象的 then 方法返回一个新的 Promise 对象,因此所以可以通过链式调用 then 方法。
我们还可以继续优化一丢丢。
参考 前端进阶面试题详细解答
async+await 语法糖
直接上代码
async 函数是在 ES2017 标准中引入的,使我们异步的代码更加优雅了。这里使用 async+await 代替了.then()方法。
async 必须在函数声明前
await 接一个 promise,那么后面的代码就会等待,等 promise resolve 了才会执行。
评论