2

I'm trying to understand the differences between es6 promises and regular callbacks but don't get the examples below. Can someone show what it would look like to do the below with callbacks?

// an immediately resolved promise
var p2 = Promise.resolve("foo"); 

// can get it after the fact, unlike events
p2.then((res) => console.log(res)); 

var p = new Promise(function(resolve, reject) {  
   setTimeout(() => resolve(4), 2000);
});

// handler can't change promise, just value
p.then((res) => {  
  res += 2;  
  console.log(res);
});

// still gets 4
p.then((res) => console.log(res));
stackjlei
  • 9,485
  • 18
  • 65
  • 113
  • 1
    A promise is a one-way latch. One it is resolved with a value or rejected with a reason, it's state and value/reason can never change. So, no matter how many times you do `.then()` on the same promise, you will always get the same result. Plain callbacks are just callbacks and they can be given a different value every time they are called. If you want to get notified once and only once when some operation completes, use a promise. If you want to get notified more than once, use a plain callback or a listener or an observer or some other mechanism that can be trigger more than once. – jfriend00 Mar 05 '17 at 00:23

3 Answers3

4

A promise is a one-way latch. Once it is resolved with a value or rejected with a reason, its state and value/reason can never change. So, no matter how many times you do .then() on the same promise, you will always get the same result. That's what "immutable" means.

I'm not sure what you mean by a guaranteed value. There is no guarantee that a promise will ever resolve. It might reject (and thus not have a value) or it might never resolve or reject if the operation just never completes.

An example of the type of operation promises are designed for is an asynchronous operations such as an Ajax call or reading some bytes from a file. The operation is asynchronous (normal execution of the interpreter continues after the operation was started) and the operation has a specific start and end to it. In most case, the operation may complete successfully in which case it can have a value or it may end with an error in which case it has an error. Both value and error can be objects so they can have many properties if the result is more than a simple value.

An Ajax call, for example has a specific start and end. It can't end more than once so it is a perfect match for promises. You get a promise that signifies the eventual result of the ajax operation. You then register both a fulfill handler and a reject handler and one or the other will be called when the operation has completed.

Plain callbacks are just callbacks and they can be given a different value every time they are called and they can be called more than once.

If you want to get notified once and only once when some operation completes and the operation has a specific begin and end, use a promise.

If you want to get notified more than once, use a plain callback or an event listener or an observer or some other mechanism that can be trigger more than once.


As a simple example, setTimeout() works very well with a promise.

function delay(t) {
    return new Promise((resolve, reject) => {
        resolve();
    }, t);
}

// delay 100ms before starting the operation
delay(100).then(run);

Or, a little more involved operation using the Bluebird Promise library to cycle through a list of URLs, download the content, parse the content, look in the content for some specific URLs and then collect them all (otherwise known as scraping):

const Promise = require('bluebird');
const request = Promise.promisifyAll(require('request'), {multiArgs: true});
const cheerio = require('cheerio');

function getAsync() {
    return request.getAsync.apply(request, arguments).then(argArray => {
        // return only the response html
        if (argArray[0].statusCode !== 200) {
            throw new Error("response statusCode = " + argArray[0].statusCode);
        }
        return argArray[1];
    });
}

const urls = [....];
Promise.mapSeries(urls, url => {
     return getAsync({url: url, gzip: true}).then(html => {
         let $ = cheerio.load(html);
         let resources = $("#external_resources_list li a.filename");
         resources.each(index, link) => {
             let href = $(link).attr("href");
             console.log(href);
             results.push(href);
         });
     }).catch(err => {
         // log error, but keep going
         console.log(url, err);
     });
}).then(() => {
    // got all results here
    console.log(results);
});

And, setInterval() does not work at all with a promise because it wants to notify you repeatedly everytime the time interval passes and that will simply not work with promises. Stick with a callback for setInterval().

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Why would a promise only let you get notified once? Doesn't a new promise get created each time you make an AJAX request? Thus, each promise corresponds with each AJAX request so then that's how you would get notified multiple times right? – stackjlei Mar 07 '17 at 16:42
  • By guaranteed, I mean how the author of the link article I had said "We are guaranteed to receive the value, regardless of when we register a handler for it, even if it's already resolved (in contrast to events, which can incur race conditions)." – stackjlei Mar 07 '17 at 16:42
  • @stackjlei - Yes, if you create a new promise and register a new `.then()` handler, there will be a new notification for a state change in that new promise. My point was that a given promise only has one state change. After that, it cannot change state and thus cannot be reused for a new async operation. – jfriend00 Mar 07 '17 at 22:09
  • @stackjlei - Yes, if a promise is resolved with a value and you install a `.then()` handler, you are guaranteed to get your `.then()` handler called with the resolved value, whether the promise is resolved before or after you install the `.then()` handler. That is a useful feature of promises. If the promise is already resolved when you install the `.then()` handler, then your `.then()` handler will get called on the next tick of the event loop. If the promise is resolved after you installed the `.then()` handler, then your handler will be called on the next tick after it's resolved. – jfriend00 Mar 07 '17 at 22:11
3

To make the comparison with a standard callback system, let's create a class that can produce such notifier objects. It will have an interface much like Promise has, but which implements a simple callback system:

class Notifier {
    constructor(executor) {
        // The object will maintain a list of callbacks
        this.callbacks = [];
        // The executor is executed now and will allow the client
        // to determine the logic of this notifier object:
        // ...when the callbacks need to be called and with which value:
        executor( (res) => {
            // The client can call this resolve function to indicate
            // the value. So now the callbacks need to be called with it:
            this.callbacks.forEach(callback => callback(res));
        });
    }
    addListener(callback) {
        // This method resembles the `then` of promises: it allows
        // a client to pass a callback function, that should be called
        // when the value becomes available (i.e. when the event triggers).
        this.callbacks.push(callback);
    }
}; 

So, like with Promise, you can pass to the constructor of this class a function to do some work and indicate the value at the appropriate time. You can also attach listeners to it, which will be called at the moment the value becomes available.

This last phrase highlights an important fact: if the value becomes available, but you did not attach a listener (callback) yet, you'll miss the notification, even if you attach the listener after the facts.

Here is the callback-based code which you could compare with the code you quoted from the article:

class Notifier {
    constructor(executor) {
        // The object will maintain a list of callbacks
        this.callbacks = [];
        // The executor is executed now and will allow the client
        // to determine the logic of this notifier object:
        // ...when the callbacks need to be called and with which value:
        executor( (res) => {
            // The client can call this resolve function to indicate
            // the value. So now the callbacks need to be called with it:
            this.callbacks.forEach(callback => callback(res));
        });
    }
    addListener(callback) {
        // This method resembles the `then` of promises: it allows
        // a client to pass a callback function, that should be called
        // when the value becomes available (i.e. when the event triggers).
        this.callbacks.push(callback);
    }
}; 

// a notifier that immediately notifies the result
f2 = new Notifier( (resolve) => resolve("foo") );
// but since no-one was listening, no callback is called.

// canNOT get it after the fact, unlike promises
f2.addListener((res) => console.log(res));
// ... nothing gets called or printed: we are too late.

// 
var f = new Notifier(function(resolve) {  
   setTimeout(() => resolve({ data: 4}), 2000);
});

// handler CAN change the outcome
f.addListener((res) => {  
  res.data += 2;
  console.log(res.data);
});

// ... now also this one gets 6!
f.addListener((res) => console.log(res.data));
trincot
  • 317,000
  • 35
  • 244
  • 286
  • When would you want to use a notifier that immediately notifies the result? I thought the whole point of promises was to be async? – stackjlei Mar 07 '17 at 16:51
  • Promises are indeed async for calling the `then` callbacks. A notifier might in general call the callback at different points in time, each time with a different purpose and value(s). It is indeed not very useful if a notifier immediately calls the callback function when it is being created. But this was intended to compare the most simple callback implementation (which does not have to guarantee to make calls asynchronously) with a promise implementation as presented in the article you quoted. – trincot Mar 07 '17 at 17:36
  • 1
    Of course, if you implement all features (and restrictions) that promises have starting from such a notifier object, then you could make it to act like a promise. See [an other answer of mine](http://stackoverflow.com/a/42057900/5459839) where I have provided code "from the ground up" that implements the promise concept. – trincot Mar 07 '17 at 17:36
-1

when the promise variable is resolved, the value resolved when it is recalled returns. to use more than one, you must call it as follows.

var p = new Promise(function(resolve, reject) {  
   setTimeout(() => resolve(4), 2000);
});

p.then((res) => {  
  res += 2;  
  console.log(res);
  return res
})
.then((res) => console.log(res));
Ahmet Şimşek
  • 1,391
  • 1
  • 14
  • 24