NodeJS回调地狱及Promise优化

    NodeJS中有很多异步API,比如常见的fs模块的readFile方法。虽然有同步的版本readFileSync, 但是其性能肯定不如前者。所以这里从异步异步版本readFile说起:

const fs = require('fs');

fs.readFile('./a.txt', 'utf-8', function(error, data) {
    if (!error) {
        console.log('a.txt data:', data);
    }
});

    函数本身比较简单,三个参数分别是文件路径,数据编码和回调函数。

    现在有这样一个需求,分别读取abc三个txt文件(文件内容分别是1, 2, 3,文件路径和js文件路径相同),按序输出文件内的内容,也就是输出1 2 3,如果并列读取,像这样:

const fs = require('fs');

fs.readFile('./a.txt', 'utf-8', function(error, data) {
    if (!error) {
        console.log('a.txt data:', data);
    }
});

fs.readFile('./b.txt', 'utf-8', function(error, data) {
    if (!error) {
        console.log('b.txt data:', data);
    }
});

fs.readFile('./c.txt', 'utf-8', function(error, data) {
    if (!error) {
        console.log('c.txt data:', data);
    }
});

多次运行,会发现每次输出的顺序不一致:

当然不止这三种,有兴趣可以多运行几次看看。

所以要出现1 2 3固定输出,得这样写,读完a.txt才能读b,读完b才能读c:

const fs = require("fs");

fs.readFile("./a.txt", "utf-8", function (error, data) {
  if (!error) {
    console.log("a.txt data:", data);
    fs.readFile("./b.txt", "utf-8", function (error, data) {
      if (!error) {
        console.log("b.txt data:", data);
        fs.readFile("./c.txt", "utf-8", function (error, data) {
          if (!error) {
            console.log("c.txt data:", data);
          }
        });
      }
    });
  }
});

    这样虽然是按序输出了,但是代码嵌套了,如果有更多文件,且读取后的处理逻辑更加复杂,整个代码的可读性就变得很差。这个就是回调地狱。为了解决这个方法,我们引入了Promise。

     Promise可以简单理解为包裹异步函数的容器,基本示例:

const fs = require("fs");

const readAPromise = new Promise(function (resolve, reject) {
  fs.readFile("./a.txt", "utf-8", function (error, data) {
    if (error) {
        reject(error);
    } else {
        resolve(data);
    }
  });
});

readAPromise.then(function(result) {
    console.log(result);
}).catch(function(error) {
    console.error(error);
});

先试试链式调用来改造回调地狱代码:

const fs = require("fs");

function readFile(filePath, defaultCoding = "utf-8") {
  return new Promise(function (resolve, reject) {
    fs.readFile(filePath, defaultCoding, function (error, data) {
      if (error) {
        reject(error);
      } else {
        resolve(data);
      }
    });
  });
}

readFile("./a.txt")
  .then(function (data) {
    console.log(data);
    return readFile("./b.txt");
  })
  .then(function (data) {
    console.log(data);
    return readFile("./c.txt");
  })
  .then(function (data) {
    console.log(data);
  })
  .catch(function (error) {
    console.error(error);
  });

然后用Promise.all试试:

const fs = require("fs");

function readFile(filePath, defaultCoding = "utf-8") {
  return new Promise(function (resolve, reject) {
    fs.readFile(filePath, defaultCoding, function (error, data) {
      if (error) {
        reject(error);
      } else {
        resolve(data);
      }
    });
  });
}

Promise.all([readFile("./a.txt"), readFile("./b.txt"), readFile("./c.txt")])
  .then(function (data) {
    console.log(data);
  })
  .catch(function (error) {
    console.error(error);
  });

          这里封装了一个返回文件读取结果Promise函数。然后调用Promise.all,第一个参数是个Promise对象数组。如果全部成功,就会到.then中的data去,data是各个promise resolve的结果数组,这里打印[ '1', '2', '3' ];如果有一个失败,整个Promise数组将走到.catch。要解决这个问题,可以尝试Promise.allSettled

const fs = require("fs");

function readFile(filePath, defaultCoding = "utf-8") {
  return new Promise(function (resolve, reject) {
    fs.readFile(filePath, defaultCoding, function (error, data) {
        if (filePath === './b.txt') {
            reject('cannot read b file');
        }
      if (error) {
        reject(error);
      } else {
        resolve(data);
      }
    });
  });
}

Promise.allSettled([readFile("./a.txt"), readFile("./b.txt"), readFile("./c.txt")])
  .then(function (data) {
    console.log(data);
  })
  .catch(function (error) {
    console.error(error);
  });

result:

[
  { status: 'fulfilled', value: '1' },
  { status: 'rejected', reason: 'cannot read b file' },
  { status: 'fulfilled', value: '3' }
]

还补充一点,使用async和await进一步优化:

异步函数基本用法:

async function f() {
  return "a";
}

// console.log(f()); // Promise { 'a' }
f()
  .then(function (data) {
    console.log(data); // a
  })
  .catch(function (error) {
    console.error(error);
  });

完整示例

const fs = require("fs");

function readFile(filePath, defaultCoding = "utf-8") {
  return new Promise(function (resolve, reject) {
    fs.readFile(filePath, defaultCoding, function (error, data) {
      if (error) {
        reject(error);
      } else {
        resolve(data);
      }
    });
  });
}

async function readAllFile() {
  const aFile = await readFile("./a.txt");
  const bFile = await readFile("./b.txt");
  const cFile = await readFile("./c.txt");
  return [aFile, bFile, cFile];
}

readAllFile().then(([a, b, c]) => console.log(a, b, c));

使用util模块的promisify方法再优化,可以去掉包装fs.readFile返回Promise函数,直接返回Promise对象而不用写回调,example:

const fs = require("fs");
const promisify = require('util').promisify;
const readFile = promisify(fs.readFile);

async function readAllFile() {
  const aFile = await readFile("./a.txt", "utf-8");
  const bFile = await readFile("./b.txt", "utf-8");
  const cFile = await readFile("./c.txt", "utf-8");
  return [aFile, bFile, cFile];
}

readAllFile().then(([a, b, c]) => console.log(a, b, c));

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值