承诺一个同步方法

Posted

技术标签:

【中文标题】承诺一个同步方法【英文标题】:Promisify a synchronous method 【发布时间】:2017-11-03 14:23:41 【问题描述】:

我可以使用promise将同步方法变成异步吗?

例如同步读取文件(是的有fs.readFile,它有回调):

// Synchronous read
var data = fs.readFileSync('input.txt'); 

我应该这样做吗:

function readFileAsync()
    return new Promise((resolve, reject) => 
        try 
          resolve(fs.readFileSync('input.txt')); 
         catch(err) 
          reject(err);
        
    )

或使用异步/等待:

 function async readFileAsync()
            try 
              let result = await fs.readFileSync('input.txt'); 
              return result;
             catch(err) 
              return err;
            
        )
    

【问题讨论】:

这样做有什么意义? @YiKai 上面的例子只是例子,重点是防止阻塞。 @Alvin 为防止阻塞,请改用fs.readFile bluebirdjs.com/docs/api/promise.promisifyall.html 查找 fs 示例 答案是关于防止阻塞的目的,不幸的是,你无法达到你的目标。但是您的示例在仅在同步方法中捕获错误的情况下很有用。例如,您有来自 API 的 variablesemail_template 并想检查它是否有效,但希望以异步方式与 promises 进行操作(因为您的架构是在 promises 上),所以这个例子将是只是这个问题的答案。 【参考方案1】:

TL;DR 不,纯同步函数不可为避免阻塞而被允许

没有。要使一个方法成为可承诺的,它需要已经是异步的,即立即返回,并在完成时使用回调。

例如:

function loop1000() 
  for (let i = 0; i < 1000; ++i) 

不是可承诺的,因为它不会立即返回并且不使用回调。但是

function loop1000(err, callback) 
  process.nextTick(() => 
    for (let i = 0; i < 1000; ++i)  
    callback();
  );

可承诺为

function loop1000promisified() 
  return new Promise((resolve, reject) => loop1000(resolve));

但是 所有这些方法无论如何都会阻塞循环。原始版本立即阻塞,使用process.nextTick() 的版本将在下一个处理器滴答声中阻塞。使应用程序在循环期间无响应。

如果您想让loop1000() 异步友好,您可以将其重写为:

function loop1000(err, callback) 
  const segmentDuration = 10;
  const loopEnd = 1000;
  let i = 0;
  function computeSegment() 
    for (let segment = 0; 
         segment < segmentDuration && i < loopEnd;
         ++segment, ++i)  
    if (i == loopEnd) 
      callback();
      return;
    
    process.nextTick(computeSegment);
  
  computeSegment();

因此,它不会有更长的阻塞时间,而是会有几个更小的阻塞。那么承诺的版本loop1000promisified() 可能会有一些意义。

免责声明:直接在 SO 上键入的代码,无需任何测试。

【讨论】:

【参考方案2】:

我会将另一个措辞重新表述为从“否”到“不是真的”的答案。

首先澄清一点:在 NodeJS 中,一切都是异步的,除了你的代码。具体来说,您的一段代码永远不会与另一段代码并行运行——但 NodeJS 运行时可能会在执行代码的同时管理其他任务(即 IO)。

fs.readFile 之类的函数的美妙之处在于 IO 与您的代码并行发生。例如:

fs.readFile("some/file",
            function(err,data)console.log("done reading file (or failed)"));
do.some("work");

第二行代码将在 NodeJS 忙于将文件读入内存时执行。 fs.readFileSync 的问题在于,当您调用它时,NodeJS 会停止评估您的代码(如果是的话!),直到 IO 完成(即在这种情况下,文件已被读入内存)。所以如果你的意思是问“你能不能把一个阻塞(大概是 IO)函数用 Promise 变成非阻塞的?”,答案肯定是“不”。

你可以使用 Promise 来控制调用阻塞函数的顺序吗?当然。 Promise 只是声明回调调用顺序的一种奇特方式——但是所有你可以用一个 Promise 来做,你可以用 setImmediate() 来做(尽管清晰度和更多的努力)。

【讨论】:

【参考方案3】:

我可以使用promise将同步方法变成异步吗?

没有。

我可以将同步方法变成异步方法吗?

没有。这就是为什么承诺在这里没有帮助的原因。您需要使用本机异步对应物,即 fs.readFile 而不是 fs.readFileSync 在您的情况下。

关于您的替代方案,您可能都不应该这样做。但是如果你绝对需要一个同步函数来返回一个已履行或被拒绝的承诺(而不是抛出异常),你可以这样做

function readFileSync()
    return new Promise(resolve => 
        resolve(fs.readFileSync('input.txt'))
    );

async function readFileSync() 
    return fs.readFileSync('input.txt');

【讨论】:

【参考方案4】:

我有点不同意其他人说你永远不应该承诺你的功能。当你想要承诺一个功能时,有些情况。例如,使用原生进程和类似进程的遗留代码库,其中没有使用回调和承诺,但您可以假设该函数是异步的并且将在一定时间内执行。

与其编写大量的 setTimeout() 回调,不如使用 Promise。

这就是我为测试目的而做的。检查 Ph 库,尤其是 promisify 函数,并检查在 before 函数中如何使用它来设置 mocha 测试。

        // Initial state
        var foo = 1;
        var xml = "";

        // Promise helper library
    var Ph = (function()
        return 
          delay: function (milis)
            var milis = milis || 200;
            return function()
              return new Promise(function(resolve, reject)
                setTimeout(function()
                  resolve();
                , milis)
              )
            

          ,

          promisify: function(syncFunc)
            return new Promise(function(resolve, reject)
              syncFunc();
              resolve();
          )
        
      
    ());


        // 'Synchronous' functions to promisify

        function setXML()
          console.log("setting XML");
          xml = "<bar>";
        

        function setVars()
          console.log("setting Vars");
          foo = 2;
        



        // Test setup

before(function(done) 
  this.timeout(0);
  Promise.resolve()
    .then(promisify(setXML))
    .then(Ph.delay(3000))
    .then(Ph.promisify(setVars))
    .then(Ph.delay(3000))
    .then(function()
      done();
    )
);


        // Test assertions

        describe("Async setup", function(done)

          it("should have XML set", function(done)
            expect(xml).to.be.not.equal("");
            done();
          );

          it("should have foo not equal 1.", function(done)
            expect(foo).to.be.not.equal(1);
            done();
          );

          it("should have foo equal to 2.", function(done)
            expect(foo).to.be.equal(2);
            done();
          );

        );

为了让它在 IE 中运行,我使用 Promise CDN:

<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.auto.min.js"></script>

【讨论】:

以上是关于承诺一个同步方法的主要内容,如果未能解决你的问题,请参考以下文章

如何同步 Promise 对象?

javascript 承诺异步或同步

将 BlueBird Promise 同步链接到一个数组中

如何同步确定JavaScript Promise的状态?

如何实现 BluebirdJS 同步检查扩展原生 Promise

如果一个同步方法调用另一个非同步方法,非同步方法是不是有锁