所谓异步,就是在函数执行后不能马上得到结果就是异步函数,常见的比如ajax.
JavaScript针对异步提供了以下六种解决方式
回调函数
回调函数是一个函数作为参数传递给另一个函数的函数,然后在需要调用的时候调用这个函数
1 | function add(a, b, callback) { |
优缺点
回调函数的优点是简单、容易理解。缺点不利于代码阅读和维护,高耦合,流程结构混乱,容易造成回调地狱。不能try catch捕获错误,不能直接return
使用场景
- 异步编程
- 事件监听
- 配合setTimeout、setInterval
事件监听
这种思路不取决于代码执行顺序,取决于事件是否触发。比如点击事件等
优缺点
可以绑定多个事件,每个事件可以指定多个回调函数
缺点:程序变成事件驱动型,运行流程不清晰
发布订阅
(与事件监听类似)
Promise/A+ 规范
promise是异步解决方案。ES6将其写进语言标准并提供了promise对象。
我们可以从promise对象内获取异步操作的结果,并使用相应回调函数处理结果
使用 详细语法
1 | let f1 = new Promise(function(resolve, reject) { |
通过 .then() 方法处理状态变化的回调函数
特点
- 状态不受外部影响,也就是说只有内部的结果能决定其状态
- 状态一经改变就不会在改变。只能从 pending => fulfilled 或 pending => rejected
应用
generator函数
generator函数首先可以理解成一个状态机,封装了多个内部状态。
执行generator函数时会返回一个遍历器对象,可以遍历函数内部的不同状态
使用
1 | function *foo(x) { |
yield表示暂停标志,next方法可启动,每次返回的是yield后面表达式的结果。
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
async/await
async/await通过generator和promise的实现,是generator函数的语法糖。
实现原理
1 | async function fn(args) { |
实现其实并不难,重要的是理解async函数的行为,返回一个promise函数,要想保持完全同步,await后面要接promise
await都做了什么
首先:
1 | async function async2() { console.log('async2')} |
1 | async function async1(){ |
问题关键就出在这个 RESOLVE 上
- RESOLVE(p)接近于Promise.resolve(p),不过有微妙而重要的区别:p 如果本身已经是 Promise 实例,Promise.resolve 会直接返回 p 而不是产生一个新 promise;
- 如果RESOLVE(p)严格按照标准,应该产生一个新的 promise,尽管该 promise 确定会 resolve 为 p,但这个过程本身是异步的,也就是现在进入 job 队列的是新 promise 的 resolve 过程,所以该 promise 的 then 不会被立即调用,而要等到当前 job 队列执行到前述 resolve 过程才会被调用,然后其回调(也就是继续 await 之后的语句)才加入 job 队列,所以时序上就晚了
await规范不断更新,这一块我还没有十分清楚
所以上述的 async1 函数我们可以进一步转换一下:
1 | function async1(){ |
例子
await后面接普通函数
1 | async function foo() { |
await后面接promise对象
1 | async function foo() { |
简单面试题-判断执行顺序
1 | async function async1() { |