Promise、async/await 使用经验

Promise

产生:改善 Callback Hell 问题

什么样的方法可以用 Promise 重写

例 1:

mysql.query('SELECT 1 + 1 AS solution', function (err, rows, fields) {
  if (err) {
    throw err;
  }
  // 对查询结果进行操作
  console.log('The solution is: ', rows[0].solution);
});

例 2:

fs.readFile('myfile.txt', function (err, file) {
  if (err) {
    throw err;
  }
  // 对file进行操作
});

如何将一个回调方法改为 Promise

Promisify

一般情况下,该方式首选。

const util = require('util');
const fs = require('fs');
 
const stat = util.promisify(fs.stat);
 
stat('.')
  .then((stats) => {
    // Do something with `stats`
  })
  .catch((error) => {
    // Handle the error.
  });

new Promise()

虽然所有的最终实现原理应该都是这样,但这种写法上的话,又再次出现了地狱回调的问题,可读性非常差。

const fs = require('fs');
 
const stat = (...args) =>
  new Promise((resolve, reject) => {
    fs.stat(...args, (err, stats) => {
      if (err) {
        reject(err);
      } else {
        resolve(stats);
      }
    });
  });
 
stat('.')
  .then((stats) => {
    // Do something with `stats`
  })
  .catch((error) => {
    // Handle the error.
  });

Defer

兼顾了可读性的一种写法。

const fs = require('fs');
 
const getDefer = () => {
  const deferred = {};
  deferred.promise = new Promise((resolve, reject) => {
    deferred.resolve = resolve;
    deferred.reject = reject;
  });
  return deferred;
};
 
const stat = (...args) => {
  const deferred = getDefer();
  fs.stat(...args, (err, stats) => {
    if (err) {
      deferred.reject(err);
    } else {
      deferred.resolve(stats);
    }
  });
  return deferred.promise;
};
 
stat('.')
  .then((stats) => {
    // Do something with `stats`
  })
  .catch((error) => {
    // Handle the error.
  });

如何调用 Promise 方法

直接执行

promiseFn()
  .then()
  .catch()
  .then() // 此处的 .then 未经过异常捕获
  .finally(); // 不管成功失败,最终都将执行

批量执行

Promise.all([promiseFn1(), promiseFn2()]).then();
 
Promise.allSettled(); // 推荐
 
Promise.race();
 
Promise.any();

async / await

自 es7 时代新增的语法糖。其主要应用场景是对于 Promise 方法调用的再次封装

什么样的方法应该用 async / await 包裹

示例:

// 原始代码
function query() {
  return mysql.query('SELECT 1 + 1 AS solution').then((rows) => {
    return rows[0].solution;
  });
}

可使用该语法糖减少嵌套:

async function query() {
  const [{ solution } = {}] = await mysql.query('SELECT 1 + 1 AS solution');
  return solution;
}

什么样的方法不需要 async / await 包裹

示例:

async function query() {
  // 一些同步操作
  const result = await mysql.query('SELECT 1 + 1 AS solution');
  return result;
  // 或者直接:
  // return await mysql.query('SELECT 1 + 1 AS solution');
}

其效果等同于:

function query() {
  // 一些同步操作
  return mysql.query('SELECT 1 + 1 AS solution');
}

并且,加上 async / await 包裹之后性能更低,因为多了一层无意义的语法糖嵌套。

并发执行

多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

const foo = await getFoo();
const bar = await getBar();

上面代码中,getFoogetBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
 
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

上面两种写法,getFoogetBar都是同时触发,这样就会缩短程序的执行时间。

p.s. 如果有多个 Promise 任务需要并发执行,建议不要使用或者慎重使用 Promise.all() 方法。

The End