Promise基础知识

动机

下面例子中的同步函数,其功能是读取指定文件并把文件内容解析为JSON对象。这个例子非常简单易读。但是在实际开发中你永远不会这么写你的代码,因为这样的代码会阻塞程序继续往下执行。也就是说这个函数一旦执行程序就只能等待文件读取完毕,期间你什么也没法做。

function readJSONSync(filename) {  
  return JSON.parse(fs.readFileSync(filename, 'utf8'));
}

为了优化我们的程序,让它更灵活,我们需要把所有的同步操作改为异步的。这时候我们可以使用回调的方式。但是回调的方式需要你更谨慎的编写代码。例如下面使用回调方式的例子就是存在问题的:

function readJSON(filename, callback){  
  fs.readFile(filename, 'utf8', function (err, res){
    if (err) return callback(err);
    callback(null, JSON.parse(res));
  });
}

问题主要有三个:

  1. readJSON这个函数的参数列表增加了一个callback,这会让人对函数的输入输出有误解。如果不是对callback这个名词有一定共识的人看到这个函数的定义,可能会以为callback是一个输入变量,实际上它确实用来获取输出结果的钩子函数。
  2. 这段代码不是在所有的控制流原语中都能正常工作。
  3. 这段代码没有处理JSON.parse的错误。

我们除了需要处理由JSON.Parse抛出的错误外,还需要注意不处理callback的错误。这时代码可以更改为下面的样子:

function readJSON(filename, callback){  
  fs.readFile(filename, 'utf8', function (err, res){
    if (err) return callback(err);
    try {
      res = JSON.parse(res);
    } catch (ex) {
      return callback(ex);
    }
    callback(null, res);
  });
}

上面的代码增加了一系列的错误处理过程,而且callback参数引起的歧义问题也没有解决。

这个时候Promise概念的引入可以解决上述问题。Promise在不需要修改底层代码实现的情况下让你做到:不需要你在定义函数是携带额外的callback参数,又可以自然地处理错误。

什么是Promise

Promise代表一个操作流程的结果。这个结果又下面三种状态

  • pending - Promise的初始状态
  • fulfilled - 成功的状态
  • rejected - 失败的状态。

Promise的状态图

需要注意的是,一但Promise处于fulfilled或者rejected状态,它就不可以再改变。

创建Promise

一般在调用以Promise方式实现的API时,API会返回Promise对象,这是你不需要自己创建Promise对象。但是,如果我们需要使用Promise重构已经存在的代码(例如使用回调函数实现的代码)时,我们可以这么做:

function readFile(filename, enc){  
  return new Promise(function (fulfill, reject){
    fs.readFile(filename, enc, function (err, res){
      if (err) reject(err);
      else fulfill(res);
    });
  });
}

我们使用new Promise创建Promise对象。该语句的参数是一个钩子函数,用来真正处理业务逻辑。当readFile被执行时,在生成Promise前这个钩子函数会被立即执行。钩子函数带有fulfill和reject两个参数,前者在你的逻辑业务成功结束的时候调用,后者在失败的时候调用。

获取Promise返回结果

为了使用Promise对象,我们必须能够获取到它的返回结果。我们可以使用Promise对象的.then函数。then函数通过钩子函数返回数据,而且可以继续处理then中处理的结果。

上面例子,我们在readJSON中调用readFile获取到Promise对象,然后在Promise的then中解析结果。

function readJSON(filename){  
  return readFile(filename, 'utf8').then(function (res){
    return JSON.parse(res)
  })
}

因为JSON.parse是一个函数,我么你可以直接把它当成钩子传入then中。如下:

function readJSON(filename){  
  return readFile(filename, 'utf8').then(JSON.parse);
}

如果想要获取JSON对象,继续下一级then即可:

readJSON('filename').then((json) => {  
    // Do what you what to do here.
});

ChardLau

继续阅读此作者的更多文章