Promisify a synchronous method

2019-08-09 04:42发布

问题:

Can I make a synchronous method into asynchronous by using promise?

For example reading a file synchronously (yes there is fs.readFile which has callback):

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

Should I do this:

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

or use async/await:

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

回答1:

I would re-phrase the the other to answers from "No" to "Not Really".

First a point of clarification: In NodeJS, everything is asynchronous, except your code. Specifically, one bit of your code will never run in parallel with another bit of your code -- but the NodeJS runtime may manage other tasks (namely IO) at the same time your code is being executed.

The beauty of functions like fs.readFile is that the IO happens in parallel with your code. For example:

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

The second line of code will be executed while NodeJS is busily reading the file into memory. The problem with fs.readFileSync is that when you call it, NodeJS stops evaluating your code (all if it!) until the IO is done (i.e. the file has been read into memory, in this case). So if you mean to ask "can you take a blocking (presumably IO) function and make it non-blocking using promises?", the answer is definitely "no".

Can you use promises to control the order in which a blocking function is called? Of course. Promises are just a fancy way of declaring the order in which call backs are called -- but everything you can do with a promise, you can do with setImmediate() (albeit with a lot less clarity and a lot more effort).



回答2:

TL;DR NO, pure synchronous functions are not promisifiable in order to avoid blockage

No. For a method to be promisifiable it needs to be already asynchronous, i.e. return immediately, and also use callbacks upon finish.

For example:

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

Is not promisifiable because it does not return immediately and does not use callbacks. But

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

Is promisifiable as

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

BUT all those approaches are going to block on the loop anyway. The original version blocks immediately and the one using process.nextTick() will block on the next processor tick. Making the application unresponsive for the duration of the loop.

If you wanted to make loop1000() asynchronous friendly you could rewrite it as:

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();
}

So instead of a longer blocking time it would have several smaller blockings. Then the promisified version loop1000promisified() could make some sense.

disclaimer: code typed directly on SO w/o any test.



回答3:

Can I make a synchronous method into asynchronous by using promise?

No.

Can I make a synchronous method into asynchronous at all?

No. That's why promises don't help here. You need to use the natively asynchronous counterpart, i.e. fs.readFile instead of fs.readFileSync in your case.

Regarding your alternatives, you probably should do neither. But if you absolutely need a synchronous function that returns a fulfilled or rejected promise (instead of throwing exceptions), you can do

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

or

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


回答4:

I would disagree slightly with the others who say you should never promisify your function. There ARE cases when you want to promisify a function. For example a legacy code base that uses native processes and similar, where no callbacks and no promises were used, but you can assume the function is async and will execute within certain time.

Instead of writing a ton of setTimeout() callbacks you want to use promises.

This is how I do it for the testing purposes. Check the Ph library, especially the promisify function, and check how it is used to set up the mocha test in before function.

        // 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();
          });

        });

To make it work in IE, I use Promise CDN:

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