Callback or Promise? [duplicate]

2019-03-03 01:26发布

问题:

This question already has an answer here:

  • Aren't promises just callbacks? 7 answers

While playing with javascript asynchronous error-catch mechanisms, I ended up asking myself what is the difference between using a promise instead of a callback, beside the fact promises are maybe more sugar syntactic.

For example, lets consider

        function setPromise() {
            var message = "awesome";
            var deferred = new $.Deferred();
            setTimeout(function(){deferred.resolve(message)},3000);
            return deferred.promise();
        }

        var promise = setPromise();

        promise.done(function (message) {
            console.log("promise done with message : " + message);
        });

and

        function setCallback(doneCallback) {
            var message = "awesome";
            setTimeout(function(){doneCallback(message)},3000);
        }

        setCallback(callback)

        function callback(message) {
            console.log("callback done with message : " + message);
        }

Both act as closure, both allow parameters to be sent back, etc.

So what are the differences?

回答1:

Promises are built on top of callbacks. The latter are more primitive, more general, and take more work when you need to do something complex.

For your example, they do pretty much the same thing. However, let's say you want to have three things resolve simultaneously (imagine requesting three resources by AJAX at the same time), and continuing when all three are done. It is trivial to do with promises, since essentially nothing changes; but with callbacks, you need to set up some flags/counters, and recognise the success and fail states yourself - much more work.



回答2:

Semantically, there really is no real difference between the two pieces of code. The message is provided to the callback sometime after a call to an initial function.

From a design standpoint, people tend to favour promises as they usually lead to easier to follow code. This is especially true where the callback is processing the result of some long-running function. Consider the following two slow-running functions:

var slowlyReturn1 = function (callback) {
  window.setTimeout(callback.call(1), 1000);
}

var slowlyReturn2 = function (callback) {
  window.setTimeout(callback.call(2), 1000);
}

Writing code that uses the results of these two long-running functions is pretty hairy:

slowlyReturn1(function(resultOf1) {
  slowlyReturn2(function(resultOf2) {
    console.log("results were: " + resultOf1 + " and " + resultOf2);
  })
});

Notice how each link in the chain of long-running functions leads to another nesting level. With promise code, you tend not to have this problem:

var slowlyReturn1 = function () {
  var d = $.Deferred();
  window.setTimeout(function () { d.resolve(1) }, 1000);
  return d.promise();
}

var slowlyReturn2 = function () {
  var d = $.Deferred();
  window.setTimeout(function () { d.resolve(2) }, 1000);
  return d.promise();
}

var resultOf1;

slowlyReturn1().then(function(r) {
  resultOf1 = resultOf1;
  return slowlyReturn2();
}).then(function(resultOf2) {
  console.log("results were: " + resultOf1 + " and " + r);
});

Also, with promise code, there tends to be a cleaner separation of concerns. The code that does the slow-running operation doesn't know how the result will be used: It simply returns something that represents that deferred result and lets the caller deal with it.

A good application of this you deal with it design surrounds exception handling. Slow running operations could .resolve() the promise, but they may also .reject() where something goes wrong. This rejection can be handled using .fail() as follows:

slowRunningOperations().then(function () {
  ...
  ...
  ... handle success
  ...
  ...
}).fail(function() {
  ...
  ... handle failure
  ...
  ...
})

Here, where the caller of the slow-running-operation doesn't care about errors, it can simply ignore them.

There are several other benefits of promise programming:

  • Most libraries that support promises provide a way to treat both regular functions and functions that return a promise the same way. They usually accomplish this by providing a function called when(). This provides a really great way to test promise code, or allow a slow function to be changed to a promise-returning-one w/o affecting callers.

  • Most libraries that support promises also provide functions for emulating more traditional control flow using promises. For example, the Q library provides allSettled(list), which takes a list of promises and returns a promise which resolves when all promises in the list have completed.

That said, as the other answer stated, promises come with a bit of overhead. Where you're not doing intense chaining, or error handling, or where you're using callbacks strictly for control-flow, you may be better off just passing functions around.