【问题标题】:Chaining methods using promises使用 Promise 链接方法
【发布时间】:2017-11-16 23:22:04
【问题描述】:

我正在努力理解承诺,我想我可以看到它们是如何工作的,例如你可以说做Step 1Step 2 然后Step 3
我使用node-fetch(使用原生 Promises)创建了这个下载功能

## FileDownload.js

const fetch = require('node-fetch');
const fs = require('fs');

module.exports = function(url, target) {
  fetch(url)
    .then(function(res) {
      var dest = fs.createWriteStream(target);
      res.body.pipe(dest);
    }).then(function(){
      console.log(`File saved at ${target}`)
    }).catch(function(err){
      console.log(err)
  });
}

所以这一切都是按顺序执行的,我可以看到它是如何工作的。

我有另一种方法可以将 CSV 文件转换为 JSON(再次使用 Promise)

## CSVToJson.js

const csvjson = require('csvjson');
const fs = require('fs');
const write_file = require('../helpers/WriteToFile');

function csvToJson(csv_file, json_path) {
  return new Promise(function(resolve, reject) {
    fs.readFile(csv_file, function(err, data){
      if (err)
        reject(err);
      else
        var data = data.toString();
        var options = {
          delimiter : ',',
          quote     : '"'
        };
      const json_data = csvjson.toObject(data, options);
      write_file(json_path, json_data)
      resolve(data);
    });
  });
}

module.exports = {
  csvToJson: csvToJson
}

当我一个接一个地调用这些函数时,第二个函数失败了,因为第一个函数没有完成。

我是否需要将这两个函数调用包装在另一个 Promise 中,即使它们各自都实现了 Promise?

如果我完全误解了这一点,请告知

【问题讨论】:

  • 你可以在你的第一个导出函数中return。这样它就会返回承诺,因此您可以在其上调用另一个 then()
  • 第一个函数错误地报告文件已写入,而实际上您所做的只是设置管道。设置管道并不意味着文件被写入;写入过程是异步的。
  • 您的csvToJson 函数在else 逻辑周围缺少关键的{},其中一些被误导性缩进,但实际上只有一部分(var data = data.toString();)是附加到else
  • 还建议您决定是否要依赖自动分号插入,然后始终包含(或省略);。 (请注意,ASI 是一种纠错机制*,它可能会影响您的决定。或者不会。:-))

标签: javascript promise


【解决方案1】:

当我一个接一个地调用这些函数时,第二个函数失败了,因为第一个函数还没有完成。

第一个有两个问题:

  1. 它不等待文件被写入;它所做的只是设置管道,而无需等待过程完成
  2. 它不提供任何方式让调用者知道进程何时完成

要处理第一个问题,您必须等待目标流上的finish 事件(pipe 返回)。要处理第二个问题,您需要返回一个在发生之前不会实现的承诺。沿着这些思路(见** cmets):

module.exports = function(url, target) {
  // ** Return the end of the chain
  return fetch(url)
    .then(function(res) {
      // ** Unfortunately, `pipe` is not Promise-enabled, so we have to resort
      // to creating a promise here
      return new Promise((resolve, reject) => {
        var dest = fs.createWriteStream(target);
        res.body.pipe(dest)
          .on('finish', () => resolve()) // ** Resolve on success
          .on('error', reject);          // ** Reject on error
      });
    }).then(result => {
      console.log(`File saved at ${target}`);
      return result;
    });
    // ** Don't `catch` here, let the caller handle it
}

然后您可以在结果上使用thencatch 进行下一步:

theFunctionAbove("/some/url", "some-target")
    .then(() = {
        // It worked, do the next thing
    })
    .catch(err => {
        // It failed
    });

(或async/await.)


旁注:我没有对其进行代码审查,但csvToJson 中的一个严重问题跳出,一个小问题也是如此,@Bergi 突出显示了第二个问题:

  1. else 逻辑周围缺少 {}

  2. 小问题是您有 var data = data.toString();data 是该函数的参数,因此 var 具有误导性(但无害)

  3. 没有正确处理readFile回调的else部分代码中的错误

我们可以通过在else 中执行resolve 并在then 处理程序中执行其余逻辑来解决这两个问题:

function csvToJson(csv_file, json_path) {
  return new Promise(function(resolve, reject) {
    fs.readFile(csv_file, function(err, data){
      if (err)
        reject(err);
      else
        resolve(data);
    });
  })
  .then(data => {
    data = data.toString();
    var options = {
      delimiter : ',',
      quote     : '"'
    };
    const json_data = csvjson.toObject(data, options);
    write_file(json_path, json_data);
    return data;
  });
}

【讨论】:

  • 非常感谢您的 cmets 和回答,我现在就完成这个
  • 如果我想进一步链接 thencatch 函数,我可以像你所做的那样添加它们还是它们会在第二个函数之后嵌套?希望这是有道理的
  • @Richlewis:如果您想在 csvToJson 完成当前工作后做其他事情,您当然可以使用 thencatch 来实现我们在那里创建的承诺去做吧。
  • @Richlewis:我应该注意到,上面的一些代码看起来很笨拙的部分原因是它混合了两个世界,旧的 Node 回调样式和 Promise。如果你打算转而使用 Promise(这是一个好主意),一般来说,通过包装像 @987654349 这样的旧式函数进入 Promise 阶段可能会更好。 @ 带有启用承诺的版本,因此在您的大多数代码中,您只处理承诺链(甚至async/await)。有关这样做的更多信息,请参阅this answer
  • 比大括号更好的建议:只将resolve(data) 放在else 中,其余的放在then 回调中,以便正确捕获异常
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-27
  • 2019-01-08
  • 2018-05-10
  • 2013-01-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多