Node.js 下 When.js Promises/A 异步编程最佳实践

发布于 2019-08-12 字数 4995 浏览 898 评论 0

假设一个业务场景:

通过 rss 地址,获取 rss 并保存于文件,rss地址保存于文件中。

完成该场景的业务需要完成3个任务:

  1. 从文件中读取rss地址。
  2. 获取rss。
  3. 保存于文件。

最后将这三个任务进行整合。

准备

存放 rss 地址的文件 address.txt。

http://www.wenjiangs.com/feed

任务1

读取 rss 地址文件的内容并通过 callback 返回。

var getRssAddress = function(path, callback) {
  fs.readFile(path, {encoding: 'utf8'}, function (err, data) {
    callback(err, data);
  });
}

任务2

通过 rss 地址 get  到 rss,并通过 callback 返回错误或数据。

var getRss = function(url, callback) {
  var data = '';
  http.get(url, function(res) {
    res.on('data', function(chrunk) {
      data += chrunk;
    });

    res.on('end', function() {
      callback(null, data);
    });
  }).on('error', function(err) {
    callback(err, null);
  });
}

任务3

将 rss 保存于文件并通过 callback 返回错误。

var saveRss = function(data, callback) {
  fs.writeFile('rss.txt', data, 'utf8', function(err) {
    callback(err);
  });
}

整合代码

getRssAddress('address.txt', function(err, data) {
  if(err) {
    console.log(err);
    return;
  }

  getRss(data, function(err, data) {
    if(err) {
      console.log(err);
      return;
    }

    saveRss(data, function(err) {
      if(err) console.log(err);
    });
  });
});

上面的代码是全异步处理,使用最常见的 callback 处理异步逻辑的返回,好处是标准写法,大家都能容易接受;坏处是耦合性太强,处理异常麻烦,代码不直观,特别是处理业务逻辑复杂和处理任务多的场景,层层的 callback 会让人眼冒金星,代码难以维护。

Promise/A 规范的实现之一 When.js 正是针对这样的问题域。

让我们来看一下改造后的代码。

任务1

var getRssAddress = function(path) {
    var deferred = when.defer();
      fs.readFile(path, {encoding: 'utf8'}, function (err, data) {
        if (err) deferred.reject(err);
        deferred.resolve(data);
      });

    return deferred.promise;
}

任务2

var getRss = function(url) {
  var deferred = when.defer();
    var data = '';
    http.get(url, function(res) {
      res.on('data', function(chrunk) {
        data += chrunk;
      });
      res.on('end', function() {
        deferred.resolve(data);
      });
    }).on('error', function(err) {
      deferred.reject(err);
    });

    return deferred.promise;
}

任务3

var saveRss = function(data) {
  var deferred = when.defer();
  fs.writeFile('rss.txt', data, 'utf8', function(err) {
    if(err) deferred.reject(err);
    deferred.resolve();
  });

  return deferred.promise;
}

整合代码

getRssAddress('address.txt')
  .then(getRss)
  .then(saveRss)
  .catch(function(err) {
    console.log(err);
  });

代码解释

Promise/A 规范定义的 Deferred/Promise 模型就是 发布/订阅者 模型,通过 Deferred 对象发布事件,可以是完成 resolve 事件,或者是失败 reject 事件;通过 Promise 对象进行对应完成或失败的订阅。

在 Promises/A 规范中,每个任务都有三种状态:默认(pending)、完成(fulfilled)、失败(rejected)。

  1. 默认状态可以单向转移到完成状态,这个过程叫resolve,对应的方法是deferred.resolve(promiseOrValue);
  2. 默认状态还可以单向转移到失败状态,这个过程叫reject,对应的方法是deferred.reject(reason);
  3. 默认状态时,还可以通过deferred.notify(update)来宣告任务执行信息,如执行进度;
  4. 状态的转移是一次性的,一旦任务由初始的pending转为其他状态,就会进入到下一个任务的执行过程中。

按照上面的代码。

通过 when.defer 定义一个 deferred 对象。

var deferred = when.defer();

异步数据获取成功后,发布一个完成事件。

deferred.resolve(data);

异步数据获取失败后,发布一个失败事件。

deferred.reject(err);

并且返回Promise对象作为订阅使用。

return deferred.promise;

订阅是通过Promise对象的then方法进行完成/失败/通知的订阅。

getRssAddress('address.txt')
  .then(getRss)

then 有三个参数,分别是 onFulfilled、onRejected、onProgress

promise.then(onFulfilled, onRejected, onProgress)

上一个任务被 resolve(data),onFulfilled函数就会被触发,data作为它的参数.

上一个任务被 reject(reason),那么onRejected就会被触发,收到reason。

任何时候,onFulfilled 和 onRejected 都只有其一可以被触发,并且只触发一次。

对于处理异常,When.js 也提供了极其方便的方法,then 能传递错误,多个任务串行执行时,我们可以只在最后一个 then 定义 onRejected。也可以在最后一个 then 的后面调用 catch 函数捕获任何一个任务的异常。

如此写法简单明了。

getRssAddress('address.txt')
  .then(getRss)
  .then(saveRss)
  .catch(function(err) {
    console.log(err);
  });

Promise 给异步编程带来了巨大的方便,可以让我们专注于单个任务的实现而不会陷入金字塔厄运,以上代码仅仅是基本使用,When.js 提供的功能远远不止本文提到的这些,具体参照官方 API。

如果你对这篇文章有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助。

扫码二维码加入Web技术交流群

您暂时不能评论!

管理员开启了需要登录才能够评论,你可以免费注册一个本站的账号。

还没有评论!

目前还没有任何评论,快来抢沙发吧!