Aren't promises just callbacks?
Promises are not callbacks. A promise represents the future result of an asynchronous operation. Of course, writing them the way you do, you get little benefit. But if you write them the way they are meant to be used, you can write asynchronous code in a way that resembles synchronous code and is much more easy to follow:
api().then(function(result){
return api2();
}).then(function(result2){
return api3();
}).then(function(result3){
// do work
});
Certainly, not much less code, but much more readable.
But this is not the end. Let's discover the true benefits: What if you wanted to check for any error in any of the steps? It would be hell to do it with callbacks, but with promises, is a piece of cake:
api().then(function(result){
return api2();
}).then(function(result2){
return api3();
}).then(function(result3){
// do work
}).catch(function(error) {
//handle any error that may occur before this point
});
Pretty much the same as a try { ... } catch
block.
Even better:
api().then(function(result){
return api2();
}).then(function(result2){
return api3();
}).then(function(result3){
// do work
}).catch(function(error) {
//handle any error that may occur before this point
}).then(function() {
//do something whether there was an error or not
//like hiding an spinner if you were performing an AJAX request.
});
And even better: What if those 3 calls to api
, api2
, api3
could run simultaneously (e.g. if they were AJAX calls) but you needed to wait for the three? Without promises, you should have to create some sort of counter. With promises, using the ES6 notation, is another piece of cake and pretty neat:
Promise.all([api(), api2(), api3()]).then(function(result) {
//do work. result is an array contains the values of the three fulfilled promises.
}).catch(function(error) {
//handle the error. At least one of the promises rejected.
});
Hope you see Promises in a new light now.
JS: what happens if to return a new Promise inside a callback of a then-statement
Promise.prototype.then()
The
then()
method returns aPromise
.
It takes up to two arguments: callback functions for the success and
failure cases of thePromise
....
Return value
Once a
Promise
is fulfilled or rejected, the respective handler
function (onFulfilled
oronRejected
) will be called asynchronously
(scheduled in the current thread loop). The behavior of the handler
function follows a specific set of rules. If a handler function:
- returns a value, the promise returned by
then
gets resolved with the returned value as its value.- doesn't return anything, the promise returned by
then
gets resolved with anundefined
value.- throws an error, the promise returned by
then
gets rejected with the thrown error as its value.- returns an already fulfilled promise, the promise returned by
then
gets fulfilled with that promise's value as its value.- returns an already rejected promise, the promise returned by
then
gets rejected with that promise's value as its value.- returns another pending promise object, the resolution/rejection of the promise returned by
then
will be subsequent to the
resolution/rejection of the promise returned by the handler. Also, the
resolved value of the promise returned bythen
will be the same as
the resolved value of the promise returned by the handler.
Source: MDN
The last (bold) bullet point is the answer to your question
Node.js: When to use Promises vs Callbacks
First off, you pretty much never want to write code that is a mix of callbacks and promises for async operations. If you're moving to promises or introducing some promises, then you probably want to refactor the callbacks in that same section of code into promises. For the appropriate types of operations, there are so many advantages of promises over plain callbacks that it is well worth the effort to convert when already working in an area of code.
Promises are great for:
- Monitoring synchronous operations
- That need to notify only once (usually completion or error)
- Coordinating or managing multiple asynchronous operations such as sequencing or branching async operations or managing multiple operations in flight at the same time
- Propagating errors from nested or deeply nested async operations
- Getting code ready for the use of async/await (or using it now with a transpiler)
- Operations that fit the Promise model where there are only three states:
pending
,fulfilled
andrejected
and where the state transitions frompending => fulfilled
or frompending => rejected
can then not change (a single one-way transition). - Dynamically linking or chaining asynchronous operations (such as do these two async operations, examine the result, then decide which other async operations to do based on the intermediate result)
- Managing a mix of asynchronous and synchronous operations
- Automatically catching and propagating upwards any exceptions that occur in async completion callbacks (in plain callbacks these exceptions are sometimes silently hidden).
Plain callbacks are good for things that promises cannot do:
- Synchronous notifications (such as the callback for
Array.prototype.map()
) - Notifications that may occur more than once (and thus need to call the callback more than once). Promises are one-shot devices and cannot be used for repeat notifications.
- Situations that cannot be mapped into the pending, fulfilled, rejected one-way state model.
And, I'd also add EventEmitter
to the mix.
EventEmitters are great for:
- Publish/subscribe type notifications
- An interface with an event model, particular when events can occur more than once (like streams)
- Loose couplings when 3rd party code wants to participate or monitor something without any more of an API than an eventEmitter. No API to design. Just make an eventEmitter public and define some events and the data that goes with them.
Notes about converting plain callback code to Promises
If your callbacks fit the node calling convention with the callback passed as the last argument and called like this callback(err, result)
, then you somewhat automatically wrap the parent function in a promise with util.promisify()
in node.js or if using the Bluebird promise library, with Promise.promisify()
.
With Bluebird, you can even promisify an entire module (that uses async callbacks in the node.js calling convention) at once such as:
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
fs.writeFileAsync("file.txt", data).then(() => {
// done here
}).catch(err => {
// error here
});
In node.js version 8+
There is now util.promisify()
which will convert an async function that uses the node.js async calling convention to a function that returns a promise.
Example from the doc:
const util = require('util');
const fs = require('fs');
const stat = util.promisify(fs.stat);
// usage of promisified function
stat('.').then((stats) => {
// Do something with `stats`
}).catch((error) => {
// Handle the error.
});
Aren't promises just callbacks?
Promises are not callbacks. A promise represents the future result of an asynchronous operation. Of course, writing them the way you do, you get little benefit. But if you write them the way they are meant to be used, you can write asynchronous code in a way that resembles synchronous code and is much more easy to follow:
api().then(function(result){
return api2();
}).then(function(result2){
return api3();
}).then(function(result3){
// do work
});
Certainly, not much less code, but much more readable.
But this is not the end. Let's discover the true benefits: What if you wanted to check for any error in any of the steps? It would be hell to do it with callbacks, but with promises, is a piece of cake:
api().then(function(result){
return api2();
}).then(function(result2){
return api3();
}).then(function(result3){
// do work
}).catch(function(error) {
//handle any error that may occur before this point
});
Pretty much the same as a try { ... } catch
block.
Even better:
api().then(function(result){
return api2();
}).then(function(result2){
return api3();
}).then(function(result3){
// do work
}).catch(function(error) {
//handle any error that may occur before this point
}).then(function() {
//do something whether there was an error or not
//like hiding an spinner if you were performing an AJAX request.
});
And even better: What if those 3 calls to api
, api2
, api3
could run simultaneously (e.g. if they were AJAX calls) but you needed to wait for the three? Without promises, you should have to create some sort of counter. With promises, using the ES6 notation, is another piece of cake and pretty neat:
Promise.all([api(), api2(), api3()]).then(function(result) {
//do work. result is an array contains the values of the three fulfilled promises.
}).catch(function(error) {
//handle the error. At least one of the promises rejected.
});
Hope you see Promises in a new light now.
Javascript Promises nest deeply just like callbacks
If a Promise is resolved with a Promise, itself does not resolve but rather waits for the passed Promise to resolve. So the Promise chain is flattened.
Same applies to returning a Promise from a then
chain.
That's quite useful:
function getFriendsList() {
return myRequest.get('/getFriendList').then(resp => {
if (needsAuthentication(resp)) {
return myRequest.post('/auth', credentials).then(() =>
myRequest.get('/getFriendList').then(resp => resp.body)
);
} else {
return resp.body;
}
});
}
function authenticate() {
return getCredentials()
.then(credentials => myRequest.post('/auth'))
.then(resp => {
if(resp.statusCode == '200'){
return resp;
} else {
throw resp;
}
})
}
Returning promises from callbacks inside async functions
It seems like this is similar to what you're doing:
async call(id: string, inputs: Array<string>): Promise<void> {
await this.loadCache();
await Promise.all(inputs.map(input => input())); // see below
await this.callInternal(id);
}
An async
function already returns a promise, so you don't need to wrap the contents of call
with a new promise.
I'm not clear what the async.each()
is doing in your example code: it's calling c
, which is the async iterator callback, but I assume you want to call k
.
It also looks like that function is synchronous, otherwise you would have passed either a callback, or you woud have prefixed the calls with await
.
I made a guess that inputs
is an array of functions that return promises, and that each function needs to be called in parallel.
Convert promises to callbacks
It's an admirable idea to learn this way, but the other thing to learn is that, just like programing, experimentation requires precision.
Turn your experiment into a "controlled experiment" by being precise about what's standardized between the functions in each style (trial), and what changes between the two trials. First, three identically formed promise operations...
function sum(a, b) {
return new Promise(resolve => setTimeout(() => resolve(a+b), 200))
}
function multiply(a, b) {
return new Promise(resolve => setTimeout(() => resolve(a*b), 200))
}
function divide(a, b) {
return new Promise(resolve => setTimeout(() => resolve(a/b), 200))
}
function run() {
// 2+3 == 5, 5*6 == 30, 30/3 == 10
return sum(2,3)
.then(s => multiply(s,6))
.then(p => divide(p,3))
.then(console.log);
}
run()
Related Topics
Firebase Query If Child of Child Contains a Value
How to Remove a Property from a JavaScript Object
JavaScript Object: Access Variable Property by Name as String
Difference Between == and === in JavaScript
JavaScript Equivalent of Python'S Zip Function
Calling Functions With Settimeout()
Getting the Id of the Element That Fired an Event
What Does [Object Object] Mean
How to Insert an Element After Another Element in JavaScript Without Using a Library
How to Create an Accurate Timer in JavaScript
"Cross Origin Requests Are Only Supported For Http." Error When Loading a Local File
How to Initialize a JavaScript Date to a Particular Time Zone
Http Get Request in JavaScript
Calculate Text Width With JavaScript
JavaScript Object Bracket Notation ({ Navigation } =) on Left Side of Assign
Events Triggered by Dynamically Generated Element Are Not Captured by Event Handler