# JS异步解决方案发展以及优缺点

上面提到了Async/await的原理,那就一定要提提js关于异步解决方案的发展之路了。

# 1)callback回调

ajax('XXX1', () => {
    // callback 函数体
    ajax('XXX2', () => {
        // callback 函数体
        ajax('XXX3', () => {
            // callback 函数体
        })
    })
})

缺点:回调地狱,不能用 try catch 捕获错误,不能 return

回调地狱的根本问题在于:

  • 缺乏顺序性: 回调地狱导致的调试困难,和大脑的思维方式不符
  • 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转)
  • 嵌套函数过多的多话,很难处理错误

# 2)Promise

Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装。

ajax('XXX1')
  .then(res => {
      // 操作逻辑
      return ajax('XXX2')
  }).then(res => {
      // 操作逻辑
      return ajax('XXX3')
  }).then(res => {
      // 操作逻辑
  })
  • 优点:比一层层的回调函数写法优雅
  • 缺点:无法取消 Promise ,错误需要通过回调函数来捕获

# 3)Generator

可以控制函数的执行,可以配合 co 函数库使用

function *fetch() {
    yield ajax('XXX1', () => {})
    yield ajax('XXX2', () => {})
    yield ajax('XXX3', () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()

# 4)Async/await

  • 优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
  • 缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。
async function test() {
  // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
  // 如果有依赖性的话,其实就是解决回调地狱的例子了
  await fetch('XXX1')
  await fetch('XXX2')
  await fetch('XXX3')
}

有一个小demo可以看一下:

let a = 0
let b = async () => {
  a = a + await 10
  console.log('2', a) // -> '2' 10
}
b()
a++
console.log('1', a) // -> '1' 1

输出如下:

// 1 1
// 2 10

因为await是异步操作,所以会先执行同步代码,所有会得到以上的执行结果。

【扩展-async/await的执行顺序】

很多人以为await会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上await是一个让出线程的标志。await后面的函数会先执行一遍,然后就会跳出整个async函数来执行后面js栈的代码。等本轮事件循环执行完了之后又会跳回到async函数中等待await后面表达式的返回值,如果返回值为非promise则继续执行async函数后面的代码,否则将返回的promise放入promise队列(Promise的Job Queue)

先看下面的代码:

function test111() {
  console.log("执行111");
  return "111 end"
}

function test222() {
  console.log("执行222");
  return "222 end"
}

async function test() {
  console.log("test start...");
  const v1 = await test111();
  console.log(v1);
  const v2 = await test222();
  console.log(v2);
  console.log(v1, v2);
}

test();

const promise = new Promise((resolve)=> { 
  console.log("promise start.."); 
  resolve("promise resolve");
});
promise.then(val=> console.log(val));

console.log("test end...")

// test start...
// 执行111
// promise start..
// test end...
// 111 end
// 执行222
// promise resolve
// 222 end
// 111 end 222 end

可以看到,代码执行顺序如下:

  • 首先执行test()函数,打印"test start..."
  • 当test函数执行到const v1 = await test()的时候,会先执行test111这个函数打印出“执行test111”的字符串,然后因为await会让出线程就会区执行后面的代码(即promise)
  • 然后打印出“promise start..”接下来会把返回的这promise放入promise队列(Promise的Job Queue),继续执行打印“test end...”
  • 等本轮事件循环执行结束后,又会跳回到async函数中(test函数),等待之前await 后面表达式的返回值,因为test111不是async函数,返回的是一个字符串“111 end”,test函数继续执行
  • 执行到const v2 = await test222()中的test222()函数,和之前一样又会跳出test函数,执行后续代码,此时事件循环就到了promise的队列,执行promise.then((val)=> console.log(val));then后面的语句,之后和前面一样又跳回到test函数继续执行
  • 再执行console.log(v2);打印v1,即打印"222 end"
  • 最后再执行console.log(v1, v2),打印"111 end 222 end"

我们将题目稍作改动,原有的test111、test222函数变为了async,返回值变为了一个Promise:

async function test111() {
  console.log("执行111");
  return Promise.resolve("111 end");
}

async function test222() {
  console.log("执行222");
  return Promise.resolve("222 end");
}

async function test() {
  console.log("test start...");
  const v1 = await test111();
  console.log(v1);
  const v2 = await test222();
  console.log(v2);
  console.log(v1, v2);
}

test();

const promise = new Promise((resolve)=> { 
  console.log("promise start.."); 
  resolve("promise resolve");
});
promise.then(val=> console.log(val));

console.log("test end...")

// test start...
// 执行111
// promise start..
// test end...
// promise resolve
// 111 end
// 执行222
// 222 end
// 111 end 222 end

这里我们发现,打印promise resolve提到111 end之前了,其他执行顺序没发生变化。原因是因为现在函数加了async,返回的是一个Promise对象要要等它resolve,所以将当前Promise推入队列,所以会继续跳出test函数执行后续代码。之后就开始执行promise的任务队列了。