JavaScript 系列之异步编程(四)

315 阅读4分钟

这是我参与8月更文挑战的第29天,活动详情查看:8月更文挑战

四、async/await 进阶

4.1 async/await 优势实战

4.1.1 抛弃冗余的 then 链式代码

function takeLongTime(n) {
  return new Promise(resolve => {
    setTimeout(() => resolve(n + 200), n);
  });
}

function step1(n) {
  console.log(`step1 with ${n}`);
  return takeLongTime(n);
}

function step2(n) {
  console.log(`step2 with ${n}`);
  return takeLongTime(n);
}

function step3(n) {
  console.log(`step3 with ${n}`);
  return takeLongTime(n);
}

用 Promise 方式来实现这三个步骤的处理:

function doIt() {
  console.time("doIt");
  const time1 = 300;
  step1(time1)
    .then(time2 => step2(time2))
    .then(time3 => step3(time3))
    .then(result => {
      console.log(`result is ${result}`);
    });
}
doIt();
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900

如果用 async/await 来实现:

async function doIt() {
  console.time("doIt");
  const time1 = 300;
  const time2 = await step1(time1);
  const time3 = await step2(time2);
  const result = await step3(time3);
  console.log(`result is ${result}`);
}
doIt();

4.1.2 更加优雅的中间值

function doIt() {
  console.time("doIt");
  const time1 = 300;
  step1(time1)
    .then(time2 => {
      return step2(time1, time2)
        .then(time3 => [time1, time2, time3]);
    })
    .then(times => {
      const [time1, time2, time3] = times;
      return step3(time1, time2, time3);
    })
    .then(result => {
      console.log(`result is ${result}`);
    });
}
doIt();

用 async/await 来写:

async function doIt() {
  console.time("doIt");
  const time1 = 300;
  const time2 = await step1(time1);
  const time3 = await step2(time1, time2);
  const result = await step3(time1, time2, time3);
  console.log(`result is ${result}`);
}
doIt();
// step1 with 300
// step2 with 300
// step3 with 300
// result is 500

4.1.3 更加易于调试

因为没有代码块,所以不能在一个返回的箭头函数中设置断点。如果你在一个 .then 代码块中使用调试器的步进(step-over)功能,调试器并不会进入后续的 .then 代码块,因为调试器只能跟踪同步代码的每一步。

image.png

现在,如果使用 async/await,你就不必再使用箭头函数。你可以对 await 语句执行步进操作,就好像他们都是普通的同步语句一样。

image.png

4.2 async/await 错误处理

因为 async 函数返回的是一个 Promise,所以我们可以在外面 catch 住错误。

const demo = async () => {
  const result = await setDelay(1000);
  console.log(result);
  console.log(await setDelaySecond(2));
  console.log(await setDelay(1000));
  console.log('完成了');
}
demo().catch(err => {
  console.log(err);
})

还可以使用 try...catch 语句,这时候就不需要在外面 catch 了。

(async () => {
  try {
    const result = await setDelay(1000);
    console.log(result);
    console.log(await setDelaySecond(2));
    console.log(await setDelay(1000));
    console.log('完成了');
  } catch (e) {
    console.log(e); // 这里捕获错误
  }
})()

但是 try...catch 好像只能包裹代码块,如果需要拆分开分别处理,不想因为一个的错误就整个 process 都 crash 掉了,那么难道要写一堆 try...catch 吗?

(async () => {
  const result = await setDelay(1000).catch(err => {
    console.log(err)
  });
  console.log(result);
  const result1 = await setDelaySecond(12).catch(err => {
    console.log(err)
  })
  console.log(result1);
  console.log(await setDelay(1000));
  console.log('完成了');
})()

输出结果:

我延迟了1000毫秒后输出的
Error: 参数必须是number类型,并且小于等于10
    at Promise (test4.html:19)
    at new Promise (<anonymous>)
    at setDelaySecond (test4.html:18)
    at test4.html:56
undefined
我延迟了1000毫秒后输出的
完成了

这样就算有错误,也不会影响后续的操作。但是 await 后面又跟着 catch,非常混乱。可以改进一下,封装一下提取错误的代码函数:

function to(promise) {
  return promise.then(data => {
    return [null, data];
  }).catch(err => [err]);
}

返回的是一个数组,第一个是错误,第二个是异步结果,使用如下:

(async () => {
  // es6的写法,返回一个数组
  // 第一个是错误信息,第二个是then的异步返回数据
  // 这里要注意一下重复变量声明可能导致问题
  [err, result] = await to(setDelay(1000))
  // 如果err存在就是有错,不想继续执行就抛出错误
  if (err) throw new Error('出现错误,同时我不想执行了');
  console.log(result);
  [err, result1] = await to(setDelaySecond(12))
  // 还想执行就不要抛出错误
  if (err) console.log('出现错误,同时我想继续执行', err);
  console.log(result1);
  console.log(await setDelay(1000));
  console.log('完成了');
})()

4.3 async/await 终止程序

Promise 本身是无法中止的,Promise 本身只是一个状态机,存储三个状态(pending,resolved,rejected),一旦发出请求了,必须闭环,无法取消,之前处于 pending 状态只是一个挂起请求的状态,并不是取消,一般不会让这种情况发生,只是用来临时中止链式的进行。

async/await 想要中断的时候,直接 return 一个值就行,null,空,false都是可以的。

let count = 6;
const demo = async () => {
  const result = await setDelay(1000);
  console.log(result);
  const result1 = await setDelaySecond(count);
  console.log(result1);
  if (count > 5) {
    return '我退出了,下面的不进行了';
    // return; 
    // return false; // 这些写法都可以
    // return null;
  }
  console.log(await setDelay(1000));
  console.log('完成了');
};

demo().then(result => {
  console.log(result);
}).catch(err => {
  console.log(err);
})

实质就是直接 return 返回了一个 Promise,相当于 return Promise.resolve('我退出了下面不进行了'),当然你也可以返回一个“拒绝”:return Promise.reject(new Error('拒绝')) 那么就会进到错误信息里去。