我如何链接多个承诺?

Posted

技术标签:

【中文标题】我如何链接多个承诺?【英文标题】:How do I chain multiple promises? 【发布时间】:2015-08-04 23:04:39 【问题描述】:

我不太确定,也许我遗漏了一些明显的东西,但我不知道如何链接两个承诺。

我的基于回调的代码如下所示:

async.series([
function (cb) 
  // Create the directory if the nodir switch isn't on
  if (!nodir) 
    fs.mkdir('somedirectory', function (err) 
      if (err) 
        log('error while trying to create the directory:\n\t %s', err)
        process.exit(0)
      
      log('successfully created directory at %s/somedirectory', process.cwd())
      cb(null)
    )
  
  cb(null)
,
function (cb) 
  // Get the contents of the sample YML
  fs.readFile(path.dirname(__dirname) + '/util/sample_config.yml', function (err, c) 
    var sampleContent = c
    if (err) 
      log('error while reading the sample file:\n\t %s', err)
      process.exit(0)
    
    log('pulled sample content...')
    // Write the config file
    fs.writeFile('db/config.yml', sampleContent, function (err) 
      if (err) 
        log('error writing config file:\n\t %s', err)
        process.exit(0)
      
      log('successfully wrote file to %s/db/config.yml', process.cwd())
      cb(null)
    )
  )

])

我已经开始尝试将其重构为基于 Promise 的流程,这就是我目前所拥有的:

if (!nodir) 
fs.mkdirAsync('somedirectory').then(function () 
  log('successfully created directory at %s/somedirectory', process.cwd())
).then(function () 
  // how can I put fs.readFileAsync here? where does it fit?
).catch(function (err) 
  log('error while trying to create the directory:\n\t %s', err)
  process.exit(0)
)

(我用的是Bluebird,所以我之前用过Promise.promisifyAll(fs)

问题是,我不知道把上一个系列的第二个“步骤”放在哪里。我是把它放在then 还是它的函数中?我是否将其返回并放在单独的函数中?

任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:

通常,promise 驱动的代码如下所示:

operation.then(function(result) 
  return nextOperation();
).then(function(nextResult) 
  return finalOperation();
).then(function(finalResult) 
)

您的示例中有很多内容,但总体思路如下:

Promise.resolve().then(function() 
  if (nodir) 
    return fs.mkdir('somedirectory').catch(function(err) 
      log('error while trying to create the directory:\n\t %s', err);
      process.exit(0);
    );
  
).then(function() 
  return fs.readFile(path.dirname(__dirname) + '/util/sample_config.yml').catch(function(err) 
    log('error while reading the sample file:\n\t %s', err);
    process.exit(0);
  )
).then(function(sampleContent) 
  log('pulled sample content...');

  return fs.writeFile('db/config.yml', sampleContent).catch(function(err) 
    log('error writing config file:\n\t %s', err)
    process.exit(0)
  )
).then(function() 
  log('successfully wrote file to %s/db/config.yml', process.cwd())
)

这假定您使用的所有调用都是 Promise Native。

原来的Promise.resolve() 只是用一个承诺来启动链,因为你的第一步是有条件的。

【讨论】:

Promise.resolve() 到底是做什么的?我经常看到它,但我从未弄清楚它的作用。编辑:我看到了你的编辑。谢谢:)【参考方案2】:

由于您使用的是 Bluebird,因此您可以使用大量的糖,并且代码比 IMO 公认的答案更简洁:

var fs = Promise.promisifyAll(fs); // tell bluebird to work with FS
Promise.try(function() 
  if(nodir) return fs.mkdirAsync('somedirectory').
                      catch(catchErr("Could not create dir"));
).then(function()
    return fs.readFileAsync(path.dirname(__dirname) + '/util/sample_config.yml').
             catch(catchErr("error while reading the sample file"));
).then(function(data)
  log('pulled sample content...');
  return fs.writeFile('db/config.yml', data).
            catch(catchErr("error writing config file"));
).then(function()
  log('successfully wrote file to %s/db/config.yml', process.cwd())
, function(err)
    // centralized error handling, to remove the redundancy
    log(err.message);
    log(err.internal);
    log(err.stack); // this is important! 
);


function catchErr(msg) // helper to rethrow with a specific message
    return function(e)
        var err = new Error(msg);
        err.internal = e; // wrap the error;
        throw e;
    ;

不过,我会更进一步,并且会删除您在此处遇到的更细粒度的错误,因为您使用的类型实际上没有提供超出这些 API 方法提供的内置消息的额外信息 - 将您的代码缩短为:

Promise.try(function()
    if(nodir) return fs.mkdirAsync("somedirectory");
).then(function()
    fs.readFileAync(path.dirname(__dirname) + '/util/sample_config.yml');
).then(function(data)
    log('pulled sample content...');
    return fs.writeFile('db/config.yml', data);
).then(function()
  log('successfully wrote file to %s/db/config.yml', process.cwd())
).catch(function(err)
    log(err);
    process.exit(1);
);

Promise 是安全的,并提供理智和认证的错误处理 - 如果你问我,这是非常好的胜利。不过,如果您使用 io.js 或现代节点,它会变得更好:

Promise.coroutine(function*() // generators ftw
  if(nodir) yield fs.mkdirAsync("somedirectory");
  var data = yield fs.readFileAsync(path.dirname(__dirname) + '/util/sample_config.yml');
  log("pulled sample content");
  yield fs.writeFileAsync("db/config.yml", data);
  log('successfully wrote file to %s/db/config.yml', process.cwd());
)();

process.on("unhandledRejection", function(p, r)
    throw r; // quit process on promise failing
);

【讨论】:

以上是关于我如何链接多个承诺?的主要内容,如果未能解决你的问题,请参考以下文章

如何在angularjs中链接promise错误函数

Javascript,如何等待多个承诺[重复]

处理多个承诺拒绝[重复]

如何运行多个firebase承诺,然后一旦完成,执行功能

等待多个承诺完成

如何在没有“快速失败”行为的情况下并行等待多个承诺? [复制]