New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Promises and synchronous operations #169

Closed
divdavem opened this Issue Jun 4, 2014 · 8 comments

Comments

Projects
None yet
5 participants
@divdavem

divdavem commented Jun 4, 2014

Hello,

First of all, I want to thank you for the good work you are doing here to make promises standard and interoperable, which is very important.

I am opening this issue to discuss a use case I came across, and to suggest an improvement to the specifications so that promises better integrate with synchronous operations.

The Promises/A+ specification states:

onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].

It is also explained in the notes:

onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack.

This is good, because, otherwise, it can become very hard to know what will be in the stack when onFullfilled and onRejected are called.

However, unless I am missing something, this specification makes it impossible to write a generic method which:

  • relies on operations which can be either synchronous or asynchronous
  • is itself synchronous when all its operations are synchronous (and asynchronous when any of its operations is asynchronous)
  • uses chains of promises

Let me explain my use case: I have implemented a CommonJS JavaScript loader for browsers (noder-js). It provides an asyncRequire function which loads a CommonJS module and returns a promise giving access to the api provided by the module.

Internally, the asyncRequire function downloads the file corresponding to the module, then it parses it, looking for all calls to require, and then, for each of those dependencies, it repeats the operation so that the full set of transitive dependencies is loaded. At the end, the module is executed.

Now let's think about this process. It is interesting to implement it with promises, because it is usually asynchronous. But the fact that it is asynchronous comes from the fact that the download method itself is asynchronous. Otherwise, the full process could be synchronous.

Now, let's suppose that I can also download files synchronously in some cases (either because I am using synchronous requests, or because I am using the loader in packaged mode, and I have already preloaded the needed files on the client). Then, there is no reason why the require should be asynchronous. And, actually, having it synchronous brings more features, because then it makes it possible to call document.write to build the page from a <script> tag inside the page, using the API provided by the synchronously loaded modules.

More generally, the current problem of the Promises/A+ specification is that it requires all the code to be asynchronous, even if it is composed only of synchronous operations.

Now here is my suggestion: let's add a thenSync method in addition to the then method. thenSync would behave exactly like then in case the promise is still pending, but it would call its callbacks synchronously in case the promise is either fulfilled or rejected.

For example:

var sync = false;
var myPromise = downloadFile(file); // can be either synchronous or asynchronous
// which means that, here, myPromise is either fulfilled or pending
myPromise.thenSync(function () {
   sync = true;
});
// if myPromise is fulfilled:
// sync == true here
// if myPromise is pending, the callback function is called asynchronously
// as if the then method had been called

The previous code is equivalent to the following code (supposing that the promise implementation has the isFulfilled and result methods):

var sync = false;
var myPromise = downloadFile(file);
var myCallback = function () {
   sync = true;
});
if (myPromise.isFulfilled()) {
   myCallback(myPromise.result());
} else {
   myPromise.then(myCallback);
}

But, for this to really work for the full chain of promises, it has to be supported in the promise library, so that, for example, the following downloadDependencies method is synchronous when all the calls to the downloadFile method are synchronous:

var downloadDependencies = function (file) {
   var myResultPromise = downloadFile(file).thenSync(function(fileContent) {
      var dependenciesList = findDependencies(fileContent);
      // now recursively call downloadDependencies for each dependency:
      var dependenciesPromises = dependenciesList.map(downloadDependencies);
      // When returning an already fulfilled promise, the promise implementation
      // should immediately mark myResultPromise as fulfilled too, without
      // waiting for the next tick
      return promise.all(dependenciesList);
   });
   return myResultPromise;
};

I have implemented the synchronous integration in the promise library of noder-js, and it allowed me, through one commit to easily add support for a synchronous mode in the loader, instead of having to rewrite everything without promises to have a synchronous equivalent.

But for this to be interoperable between different promises implementations, it has to be specified in the Promises/A+ specification. That is why I am opening this issue.

Now my questions are: what do you think about my suggestion? Do you have other suggestions? How would you handle my use case?

Thank you in advance for your responses.

@timjansen

This comment has been minimized.

Show comment
Hide comment
@timjansen

timjansen Jun 4, 2014

Contributor

I am curious what's the use case for executing code synchronously in
asyncRequire() when there is no guarantee that it will be executed
synchronously.

You mention that it's possible to use document.write when it is called
synchronously. That's certainly true, but if asyncRequire() may execute the
Promise asynchronously, this won't work. So it is of no use unless there is
some way of knowing that asyncRequire() is actually able to fulfill the
operation synchronously. But if the user can already be sure that the
resource is already available at the time she is writing the code, wouldn't
it be easier and more convenient to call a synchronous variant (require(),
I suppose) in the first place?

Tim

On Wed, Jun 4, 2014 at 5:14 PM, divdavem notifications@github.com wrote:

Hello,

First of all, I want to thank you for the good work you are doing here to
make promises standard and interoperable, which is very important.

I am opening this issue to discuss a use case I came across, and to
suggest an improvement to the specifications so that promises better
integrate with synchronous operations.

The Promises/A+ specification states:

onFulfilled or onRejected must not be called until the execution context
stack contains only platform code. [3.1].

It is also explained in the notes:

onFulfilled and onRejected execute asynchronously, after the event loop
turn in which then is called, and with a fresh stack.

This is good, because, otherwise, it can become very hard to know what
will be in the stack when onFullfilled and onRejected are called.

However, unless I am missing something, this specification makes it
impossible to write a generic method which:

  • relies on operations which can be either synchronous or asynchronous
  • is itself synchronous when all its operations are synchronous (and
    asynchronous when any of its operations is asynchronous)
  • uses chains of promises

Let me explain my use case: I have implemented a CommonJS JavaScript
loader for browsers (noder-js https://github.com/ariatemplates/noder-js).
It provides an asyncRequire function which loads a CommonJS module and
returns a promise giving access to the api provided by the module.

Internally, the asyncRequire function downloads the file corresponding to
the module, then it parses it, looking for all calls to require, and
then, for each of those dependencies, it repeats the operation so that the
full set of transitive dependencies is loaded. At the end, the module is
executed.

Now let's think about this process. It is interesting to implement it with
promises, because it is usually asynchronous. But the fact that it is
asynchronous comes from the fact that the download method itself is
asynchronous. Otherwise, the full process could be synchronous.

Now, let's suppose that I can also download files synchronously in some
cases (either because I am using synchronous requests, or because I am
using the loader in packaged mode, and I have already preloaded the needed
files on the client). Then, there is no reason why the require should be
asynchronous. And, actually, having it synchronous brings more features,
because then it makes it possible to call document.write to build the
page from a <script> tag inside the page, using the API provided by the
synchronously loaded modules.

More generally, the current problem of the Promises/A+ specification is
that it requires all the code to be asynchronous, even if it is composed
only of synchronous operations.

Now here is my suggestion: let's add a thenSync method in addition to the
then method. thenSync would behave exactly like then in case the promise
is still pending, but it would call its callbacks synchronously in case the
promise is either fulfilled or rejected.

For example:

var sync = false;var myPromise = downloadFile(file); // can be either synchronous or asynchronous// which means that, here, myPromise is either fulfilled or pendingmyPromise.thenSync(function () {
sync = true;});// if myPromise is fulfilled:// sync == true here// if myPromise is pending, the callback function is called asynchronously// as if the then method had been called

The previous code is equivalent to the following code (supposing that the
promise implementation has the isFulfilled and result methods):

var sync = false;var myPromise = downloadFile(file);var myCallback = function () {
sync = true;});if (myPromise.isFulfilled()) {
myCallback(myPromise.result());} else {
myPromise.then(myCallback);}

But, for this to really work for the full chain of promises, it has to be
supported in the promise library, so that, for example, the following
downloadDependencies method is synchronous when all the calls to the
downloadFile method are synchronous:

var downloadDependencies = function (file) {
var myResultPromise = downloadFile(file).thenSync(function(fileContent) {
var dependenciesList = findDependencies(fileContent);
// now recursively call downloadDependencies for each dependency:
var dependenciesPromises = dependenciesList.map(downloadDependencies);
// When returning an already fulfilled promise, the promise implementation
// should immediately mark myResultPromise as fulfilled too, without
// waiting for the next tick
return promise.all(dependenciesList);
});
return myResultPromise;};

I have implemented the synchronous integration in the promise library of
noder-js, and it allowed me, through one commit
ariatemplates/noder-js@e5faac3
to easily add support for a synchronous mode in the loader, instead of
having to rewrite everything without promises to have a synchronous
equivalent.

But for this to be interoperable between different promises
implementations, it has to be specified in the Promises/A+ specification.
That is why I am opening this issue.

Now my questions are: what do you think about my suggestion? Do you have
other suggestions? How would you handle my use case?

Thank you in advance for your responses.


Reply to this email directly or view it on GitHub
#169.

Contributor

timjansen commented Jun 4, 2014

I am curious what's the use case for executing code synchronously in
asyncRequire() when there is no guarantee that it will be executed
synchronously.

You mention that it's possible to use document.write when it is called
synchronously. That's certainly true, but if asyncRequire() may execute the
Promise asynchronously, this won't work. So it is of no use unless there is
some way of knowing that asyncRequire() is actually able to fulfill the
operation synchronously. But if the user can already be sure that the
resource is already available at the time she is writing the code, wouldn't
it be easier and more convenient to call a synchronous variant (require(),
I suppose) in the first place?

Tim

On Wed, Jun 4, 2014 at 5:14 PM, divdavem notifications@github.com wrote:

Hello,

First of all, I want to thank you for the good work you are doing here to
make promises standard and interoperable, which is very important.

I am opening this issue to discuss a use case I came across, and to
suggest an improvement to the specifications so that promises better
integrate with synchronous operations.

The Promises/A+ specification states:

onFulfilled or onRejected must not be called until the execution context
stack contains only platform code. [3.1].

It is also explained in the notes:

onFulfilled and onRejected execute asynchronously, after the event loop
turn in which then is called, and with a fresh stack.

This is good, because, otherwise, it can become very hard to know what
will be in the stack when onFullfilled and onRejected are called.

However, unless I am missing something, this specification makes it
impossible to write a generic method which:

  • relies on operations which can be either synchronous or asynchronous
  • is itself synchronous when all its operations are synchronous (and
    asynchronous when any of its operations is asynchronous)
  • uses chains of promises

Let me explain my use case: I have implemented a CommonJS JavaScript
loader for browsers (noder-js https://github.com/ariatemplates/noder-js).
It provides an asyncRequire function which loads a CommonJS module and
returns a promise giving access to the api provided by the module.

Internally, the asyncRequire function downloads the file corresponding to
the module, then it parses it, looking for all calls to require, and
then, for each of those dependencies, it repeats the operation so that the
full set of transitive dependencies is loaded. At the end, the module is
executed.

Now let's think about this process. It is interesting to implement it with
promises, because it is usually asynchronous. But the fact that it is
asynchronous comes from the fact that the download method itself is
asynchronous. Otherwise, the full process could be synchronous.

Now, let's suppose that I can also download files synchronously in some
cases (either because I am using synchronous requests, or because I am
using the loader in packaged mode, and I have already preloaded the needed
files on the client). Then, there is no reason why the require should be
asynchronous. And, actually, having it synchronous brings more features,
because then it makes it possible to call document.write to build the
page from a <script> tag inside the page, using the API provided by the
synchronously loaded modules.

More generally, the current problem of the Promises/A+ specification is
that it requires all the code to be asynchronous, even if it is composed
only of synchronous operations.

Now here is my suggestion: let's add a thenSync method in addition to the
then method. thenSync would behave exactly like then in case the promise
is still pending, but it would call its callbacks synchronously in case the
promise is either fulfilled or rejected.

For example:

var sync = false;var myPromise = downloadFile(file); // can be either synchronous or asynchronous// which means that, here, myPromise is either fulfilled or pendingmyPromise.thenSync(function () {
sync = true;});// if myPromise is fulfilled:// sync == true here// if myPromise is pending, the callback function is called asynchronously// as if the then method had been called

The previous code is equivalent to the following code (supposing that the
promise implementation has the isFulfilled and result methods):

var sync = false;var myPromise = downloadFile(file);var myCallback = function () {
sync = true;});if (myPromise.isFulfilled()) {
myCallback(myPromise.result());} else {
myPromise.then(myCallback);}

But, for this to really work for the full chain of promises, it has to be
supported in the promise library, so that, for example, the following
downloadDependencies method is synchronous when all the calls to the
downloadFile method are synchronous:

var downloadDependencies = function (file) {
var myResultPromise = downloadFile(file).thenSync(function(fileContent) {
var dependenciesList = findDependencies(fileContent);
// now recursively call downloadDependencies for each dependency:
var dependenciesPromises = dependenciesList.map(downloadDependencies);
// When returning an already fulfilled promise, the promise implementation
// should immediately mark myResultPromise as fulfilled too, without
// waiting for the next tick
return promise.all(dependenciesList);
});
return myResultPromise;};

I have implemented the synchronous integration in the promise library of
noder-js, and it allowed me, through one commit
ariatemplates/noder-js@e5faac3
to easily add support for a synchronous mode in the loader, instead of
having to rewrite everything without promises to have a synchronous
equivalent.

But for this to be interoperable between different promises
implementations, it has to be specified in the Promises/A+ specification.
That is why I am opening this issue.

Now my questions are: what do you think about my suggestion? Do you have
other suggestions? How would you handle my use case?

Thank you in advance for your responses.


Reply to this email directly or view it on GitHub
#169.

@divdavem

This comment has been minimized.

Show comment
Hide comment
@divdavem

divdavem Jun 4, 2014

@timjansen Thank you for your remark.
Sure, the end user calls the synchronous variant.
But the implementation of the synchronous variant is in fact only a wrapper which calls the asynchronous one, and raises an error in case the promise is not synchronously fulfilled.
This avoids maintaining 2 different versions of the code: one fully synchronous and one fully asynchronous, because, in fact, it is the same algorithm.

divdavem commented Jun 4, 2014

@timjansen Thank you for your remark.
Sure, the end user calls the synchronous variant.
But the implementation of the synchronous variant is in fact only a wrapper which calls the asynchronous one, and raises an error in case the promise is not synchronously fulfilled.
This avoids maintaining 2 different versions of the code: one fully synchronous and one fully asynchronous, because, in fact, it is the same algorithm.

@bergus

This comment has been minimized.

Show comment
Hide comment
@bergus

bergus Jun 4, 2014

You should write it the other way round. Put the code that can be synchronous in the synchronous method, and return failure when the module cannot be fetched synchronously.
Then in the asynchronous one, call the synchronous one and return a promise of its result, or load the module asynchronously when it's not available and return a promise for that.

Quite literally:

function requireAsync(…) {
    return Promise.try(requireSync, …).fail(loadAsync);
}

bergus commented Jun 4, 2014

You should write it the other way round. Put the code that can be synchronous in the synchronous method, and return failure when the module cannot be fetched synchronously.
Then in the asynchronous one, call the synchronous one and return a promise of its result, or load the module asynchronously when it's not available and return a promise for that.

Quite literally:

function requireAsync(…) {
    return Promise.try(requireSync, …).fail(loadAsync);
}
@divdavem

This comment has been minimized.

Show comment
Hide comment
@divdavem

divdavem Jun 5, 2014

@bergus This still obliges me to write and maintain 2 versions of the same algorithm: one synchronous and one asynchronous, which I want to avoid.

divdavem commented Jun 5, 2014

@bergus This still obliges me to write and maintain 2 versions of the same algorithm: one synchronous and one asynchronous, which I want to avoid.

@timjansen

This comment has been minimized.

Show comment
Hide comment
@timjansen

timjansen Jun 5, 2014

Contributor

@disdavem Your suggestion of adding thenSync() to Promises/A+ does not help
you if you want to have only one version. You could use the synchronous
algorithm for thenSync(), but you'd still need to implement it
asynchronously for then().

An option could be to modify a simple Promises/A+ implementation (e.g.
PinkySwear.js) and add an option to the constructor that allows calling the
handler synchronously. When you use this modified promises implementation
internally, for example to implement require(), you enable that option and
can use the asynchronous operation to implement the synchronous
implementation. But if I user calls requireAsync(), you disable the option
and the returned promise will behave like a real Promises/A+ implementation.

Tim

On Thu, Jun 5, 2014 at 9:20 AM, divdavem notifications@github.com wrote:

@bergus https://github.com/bergus This still obliges me to write and
maintain 2 versions of the same algorithm: one synchronous and one
asynchronous, which I want to avoid.


Reply to this email directly or view it on GitHub
#169 (comment)
.

Contributor

timjansen commented Jun 5, 2014

@disdavem Your suggestion of adding thenSync() to Promises/A+ does not help
you if you want to have only one version. You could use the synchronous
algorithm for thenSync(), but you'd still need to implement it
asynchronously for then().

An option could be to modify a simple Promises/A+ implementation (e.g.
PinkySwear.js) and add an option to the constructor that allows calling the
handler synchronously. When you use this modified promises implementation
internally, for example to implement require(), you enable that option and
can use the asynchronous operation to implement the synchronous
implementation. But if I user calls requireAsync(), you disable the option
and the returned promise will behave like a real Promises/A+ implementation.

Tim

On Thu, Jun 5, 2014 at 9:20 AM, divdavem notifications@github.com wrote:

@bergus https://github.com/bergus This still obliges me to write and
maintain 2 versions of the same algorithm: one synchronous and one
asynchronous, which I want to avoid.


Reply to this email directly or view it on GitHub
#169 (comment)
.

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Jun 5, 2014

Member

The way to do this is to have a promise like object that is not A+ compatible but is close to being a Promise. It makes sense for it to be a separate type so that you can force it to always be synchronous. Consider the following synchronous promise implementation:

function FakePromise(value, rejected) {
  if (value instanceof FakePromise) {
    this.then = value.then.bind(value);
  } else {
    if (!rejected && value && (typeof value === 'object' || typeof value === 'function') && typeof value.then === 'function') {
      value = new Error('Cannot create a FakePromise for a RealPromise.');
      rejected = true;
    }
    this.then = function (onFulfilled, onRejected) {
      var fn = rejected ? onRejected : onFulfilled;
      if (typeof fn !== 'function') return this
      try {
        return new FakePromise(fn(value), false);
      } catch (ex) {
        return new FakePromise(ex, true);
      }
    };
  }
}
FakePromise.prototype.getValue = function () {
  var value, rejected;
  this.then(function (val) {
    value = val;
    rejected = false;
  }, function (val) {
    value = val;
    rejected = false;
  });
  if (rejected) throw value;
  else return value;
};
FakePromise.denodeify = function (fn) {
  return function () {
    try {
      return new FakePromise(fn.apply(this, arguments), false);
    } catch (ex) {
      return new FakePromise(ex, true);
    }
  }
};
FakePromise.unwrap = function (value) {
  if (value instanceof FakePromise) return value.getValue();
  else return value;
};

It has no means for you to create an asynchronous promise. This is deliberate. You could then write a mkdirp algorithm like the following. Note how mkdir and stat are required to be passed in by the consumer:

function mkdirpFactory(mkdir, stat) {
  function mkdirp(p, mode, made) {
    if (mode === undefined) {
      mode = 0777 & (~process.umask());
    }
    if (!made) made = null;

    if (typeof mode === 'string') mode = parseInt(mode, 8);
    p = path.resolve(p);

    return mkdir(p, mode).then(function () {
      made = made || p;
    }).then(null, function (err0) {
      switch (err0.code) {
        case 'ENOENT':
          return mkdirp(path.dirname(p), mode, made).then(function () {
            return rec(p, mode, made);
          });

        // In the case of any other error, just see if there's a dir
        // there already.  If so, then hooray!  If not, then something
        // is borked.
        default:
          return stat(p).then(function (stat) {
            if (!stat.isDirectory()) throw err0;
          }, function (err1) {
            throw err0;
          });
          break;
      }
    }).then(function () {
      return made;
    });
  }
  return function mkdirpResult(p, mode, made) {
    return FakePromise.unwrap(mkdirp(p, mode, made));
  }
  return mkdirpResult;
}

We can then obtain the two separate implementations of mkdirp (sync and async) simply with:

var Promise = require('promise');

var mkdirp = mkdirpFactory(Promise.denodeify(fs.mkdir), Promise.denodeify(fs.stat));
var mkdirpSync = mkdirpFactory(FakePromise.denodeify(fs.mkdirSync), FakePromise.denodeify(fs.statSync));

It doesn't require modifying this specification because the FakePromise is not a Promise. It is a special class all of its own. It's only use case is in implementing algorithms that are sometimes synchronous and sometimes asynchronous.

Member

ForbesLindesay commented Jun 5, 2014

The way to do this is to have a promise like object that is not A+ compatible but is close to being a Promise. It makes sense for it to be a separate type so that you can force it to always be synchronous. Consider the following synchronous promise implementation:

function FakePromise(value, rejected) {
  if (value instanceof FakePromise) {
    this.then = value.then.bind(value);
  } else {
    if (!rejected && value && (typeof value === 'object' || typeof value === 'function') && typeof value.then === 'function') {
      value = new Error('Cannot create a FakePromise for a RealPromise.');
      rejected = true;
    }
    this.then = function (onFulfilled, onRejected) {
      var fn = rejected ? onRejected : onFulfilled;
      if (typeof fn !== 'function') return this
      try {
        return new FakePromise(fn(value), false);
      } catch (ex) {
        return new FakePromise(ex, true);
      }
    };
  }
}
FakePromise.prototype.getValue = function () {
  var value, rejected;
  this.then(function (val) {
    value = val;
    rejected = false;
  }, function (val) {
    value = val;
    rejected = false;
  });
  if (rejected) throw value;
  else return value;
};
FakePromise.denodeify = function (fn) {
  return function () {
    try {
      return new FakePromise(fn.apply(this, arguments), false);
    } catch (ex) {
      return new FakePromise(ex, true);
    }
  }
};
FakePromise.unwrap = function (value) {
  if (value instanceof FakePromise) return value.getValue();
  else return value;
};

It has no means for you to create an asynchronous promise. This is deliberate. You could then write a mkdirp algorithm like the following. Note how mkdir and stat are required to be passed in by the consumer:

function mkdirpFactory(mkdir, stat) {
  function mkdirp(p, mode, made) {
    if (mode === undefined) {
      mode = 0777 & (~process.umask());
    }
    if (!made) made = null;

    if (typeof mode === 'string') mode = parseInt(mode, 8);
    p = path.resolve(p);

    return mkdir(p, mode).then(function () {
      made = made || p;
    }).then(null, function (err0) {
      switch (err0.code) {
        case 'ENOENT':
          return mkdirp(path.dirname(p), mode, made).then(function () {
            return rec(p, mode, made);
          });

        // In the case of any other error, just see if there's a dir
        // there already.  If so, then hooray!  If not, then something
        // is borked.
        default:
          return stat(p).then(function (stat) {
            if (!stat.isDirectory()) throw err0;
          }, function (err1) {
            throw err0;
          });
          break;
      }
    }).then(function () {
      return made;
    });
  }
  return function mkdirpResult(p, mode, made) {
    return FakePromise.unwrap(mkdirp(p, mode, made));
  }
  return mkdirpResult;
}

We can then obtain the two separate implementations of mkdirp (sync and async) simply with:

var Promise = require('promise');

var mkdirp = mkdirpFactory(Promise.denodeify(fs.mkdir), Promise.denodeify(fs.stat));
var mkdirpSync = mkdirpFactory(FakePromise.denodeify(fs.mkdirSync), FakePromise.denodeify(fs.statSync));

It doesn't require modifying this specification because the FakePromise is not a Promise. It is a special class all of its own. It's only use case is in implementing algorithms that are sometimes synchronous and sometimes asynchronous.

@divdavem

This comment has been minimized.

Show comment
Hide comment
@divdavem

divdavem Jun 5, 2014

@timjansen
Thank you for your response.
In fact, my suggestion of adding thenSync to Promises/A+ did help me to have only one version of the algorithm: that version uses thenSync nearly everywhere, instead of then because using then would make the whole process asynchronous, which would not work in the synchronous case.
Now, in my opinion, having to modify a Promises/A+ implementation to implement what I need underlines a weakness of the Promises/A+ specification: it does not cover well some use cases. My suggestion tries to remove that weakness from the specification and to better cover the synchronous use case.
Thank you for your advice to use your promise implementation PinkySwear.js. It is interesting as it is quite small. However, we actually already have our implementation of promises integrated in noder-js. It contains the non-standard (or not yet standard?) thenSync method, and is compliant with an earlier version of the Promises/A+ specification (before it was required to support resolving a promise with another promise).

@ForbesLindesay
Thank you for your suggestion. It is a smart solution.
However, I still prefer my solution for the following reasons:

  • In your solution, there is a clear separation between two different kinds of promises: fake promises for synchronous functions and real promises for asynchronous functions. However, in my opinion, an already fulfilled real promise and a fake promise should be treated the same way.
  • Fake promises break the Promises/A+ specifications, we are probably not supposed to return them from a public API. That is a problem, because as soon as a Promises/A+ strictly compliant promise is returned, the ability to have a result synchronously is lost. This matters especially when building user interfaces, to avoid flickering effects.

Let's still consider my asyncRequire method. It is usually asynchronous, so if I implemented your solution @ForbesLindesay, it would use the real promise implementation. It is part of the public API, so it is supposed to only return Promises/A+ compliant promises. Now asyncRequire has a cache, because we do not want to reload already loaded modules.

In the user interface, when the user does an operation which triggers the call of asyncRequire for a specific module for the first time, a "Please wait" message is displayed to provide a feedback while the module and its dependencies are downloaded asynchronously.

If the module is already loaded, there is no need to display the "Please wait" message. However the Promises/A+ specification provides no way to know that the promise is already fulfilled (and in fact, it is perhaps still pending if there is a promises chain which uses then, which adds unnecessary asynchronism). As a consequence, the "Please wait" message is displayed and is removed quickly after, when the onFulfilled method is called (which happens asynchronously), leading to a flickering effect.

Of course, it is possible to work around the problem and first call another function to check whether the call to asyncRequire can complete synchronously and then to have two branches in the code: one for the synchronous case and the other one for the asynchronous one, but it makes the code more complex, and it is much more convenient to just call thenSync and have common code for synchronous and asynchronous cases.

Please update the specifications to solve this issue, so that it is not mandatory to add unneeded asynchronism when working with standard A+ promises, and so that it is possible for multiple implementations to synchronously interoperate. Especially, there should be a standard way to synchronously get the result of a promise, and there should be a convenient way to write code which works both in the synchronous and asynchronous cases. My suggestion with thenSync is a solution. Perhaps there are others. But, please deal with this problem in the specifications.

If this use case is not well covered in the standard, of course, it will be needed to write non-standard compliant code for those use cases (as suggested by @timjansen), but this could go beyond the internal implementation, because synchronism must be preserved for the whole promise chain.

divdavem commented Jun 5, 2014

@timjansen
Thank you for your response.
In fact, my suggestion of adding thenSync to Promises/A+ did help me to have only one version of the algorithm: that version uses thenSync nearly everywhere, instead of then because using then would make the whole process asynchronous, which would not work in the synchronous case.
Now, in my opinion, having to modify a Promises/A+ implementation to implement what I need underlines a weakness of the Promises/A+ specification: it does not cover well some use cases. My suggestion tries to remove that weakness from the specification and to better cover the synchronous use case.
Thank you for your advice to use your promise implementation PinkySwear.js. It is interesting as it is quite small. However, we actually already have our implementation of promises integrated in noder-js. It contains the non-standard (or not yet standard?) thenSync method, and is compliant with an earlier version of the Promises/A+ specification (before it was required to support resolving a promise with another promise).

@ForbesLindesay
Thank you for your suggestion. It is a smart solution.
However, I still prefer my solution for the following reasons:

  • In your solution, there is a clear separation between two different kinds of promises: fake promises for synchronous functions and real promises for asynchronous functions. However, in my opinion, an already fulfilled real promise and a fake promise should be treated the same way.
  • Fake promises break the Promises/A+ specifications, we are probably not supposed to return them from a public API. That is a problem, because as soon as a Promises/A+ strictly compliant promise is returned, the ability to have a result synchronously is lost. This matters especially when building user interfaces, to avoid flickering effects.

Let's still consider my asyncRequire method. It is usually asynchronous, so if I implemented your solution @ForbesLindesay, it would use the real promise implementation. It is part of the public API, so it is supposed to only return Promises/A+ compliant promises. Now asyncRequire has a cache, because we do not want to reload already loaded modules.

In the user interface, when the user does an operation which triggers the call of asyncRequire for a specific module for the first time, a "Please wait" message is displayed to provide a feedback while the module and its dependencies are downloaded asynchronously.

If the module is already loaded, there is no need to display the "Please wait" message. However the Promises/A+ specification provides no way to know that the promise is already fulfilled (and in fact, it is perhaps still pending if there is a promises chain which uses then, which adds unnecessary asynchronism). As a consequence, the "Please wait" message is displayed and is removed quickly after, when the onFulfilled method is called (which happens asynchronously), leading to a flickering effect.

Of course, it is possible to work around the problem and first call another function to check whether the call to asyncRequire can complete synchronously and then to have two branches in the code: one for the synchronous case and the other one for the asynchronous one, but it makes the code more complex, and it is much more convenient to just call thenSync and have common code for synchronous and asynchronous cases.

Please update the specifications to solve this issue, so that it is not mandatory to add unneeded asynchronism when working with standard A+ promises, and so that it is possible for multiple implementations to synchronously interoperate. Especially, there should be a standard way to synchronously get the result of a promise, and there should be a convenient way to write code which works both in the synchronous and asynchronous cases. My suggestion with thenSync is a solution. Perhaps there are others. But, please deal with this problem in the specifications.

If this use case is not well covered in the standard, of course, it will be needed to write non-standard compliant code for those use cases (as suggested by @timjansen), but this could go beyond the internal implementation, because synchronism must be preserved for the whole promise chain.

@domenic

This comment has been minimized.

Show comment
Hide comment
@domenic

domenic Jun 9, 2014

Member

Promises/A+ promises are asynchronous. If you want synchronous behavior, then you are not using real promises, and certainly not Promises/A+ compliant ones, and you should just not use promises.

Member

domenic commented Jun 9, 2014

Promises/A+ promises are asynchronous. If you want synchronous behavior, then you are not using real promises, and certainly not Promises/A+ compliant ones, and you should just not use promises.

@domenic domenic closed this Jun 9, 2014

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment