They don't handle streaming data, but they're not supposed to; they're supposed to model the traditional JS function call model (single value return or single value exception) which they do well. For streaming data use some other paradigm (perhaps Observables from https://github.com/jhusain/asyncgenerator), but that's orthogonal to Promises, not a replacement for them.
The only other reasonable complaint I've heard is that they aren't cancellable, which is legit. There are proposals for that, though (https://github.com/petkaantonov/bluebird/blob/master/API.md#...).
At least with Promises functions actually return, as opposed to the continuation-passing-style of callbacks. (CPS is a fine intermediate representation for compilers, but it really shouldn't be written by hand; that's why we have compilers!)
For example, let's take the wonderful async.js library. There are a variety of different tools at my disposal on picking how to run tasks in parallel. My favorite one is async.auto. With this method, I can define the asynchronous tasks and their dependencies, then async.auto finds the best way to run the tasks in parallel while still ensuring the tasks end up having their dependencies met. This is more in tune to declarative programming's ideal of defining what results you want, rather than specifying how to accomplish getting those results.
Now with ES7's async/await, everything is still sequential. Better than blocking synchronous programming, but it could be better.
Here's an example of some code I had to write to query a somewhat nasty Web API:
async function apiCall(method, path, qs, body) { ... }
async function getPaginated(path, qs, extractionFn) {
let data = [];
let pagePromises = [
apiCall('GET', path, Object.assign({ }, qs, { page: 1 }))
];
let firstPage = await pagePromises[0];
for (let page = 2; page <= firstPage.pagination.page_count; ++page) {
pagePromises.push(
apiCall('GET', path, Object.assign({ }, qs, { page }))
);
}
let pages = await Promise.all(pagePromises);
for (let page of pages) {
let extracted = await extractionFn(page, qs);
data = data.concat(extracted);
}
return data;
}
Note that because of how promises work I didn't have to special case the storage of the first page (which needs to be fetched first to get the number of pages); I can just wait on it again and get the promise-cached answer. I don't know what async.js code to do that would look like but I doubt it'd be as succinct.async functions magically return promises. You don't have to just await them, you can choose to do whatever you want with the resulting promises. And promises, being actual values and not just functions called at the right time, are much easier to compose.
Anyway, if you like the async.auto functionality, it wouldn't be very hard to write a Promise.auto that would let you do (compare to the async.auto example at https://github.com/caolan/async#auto):
let results = await Promise.auto({
get_data: async function(){
console.log('in get_data');
// async code to get some data
return [ 'data', 'converted to array' ];
},
make_folder: async function(){
console.log('in make_folder');
// async code to create a directory to store a file in
// this is run at the same time as getting the data
return 'folder';
},
write_file: ['get_data', 'make_folder', async function(results){
console.log('in write_file', JSON.stringify(results));
// once there is some data and the directory exists,
// write the data to a file in the directory
return 'filename';
}],
email_link: ['write_file', async function({write_file}){
console.log('in email_link', JSON.stringify(results));
// once the file is written let's email a link to it...
// results.write_file contains the filename returned by write_file.
return {'file':write_file, 'email':'user@example.com'};
}]
});
console.log('results = ', results);
...which is the same functionality except you don't have explicit callbacks running around everywhere. For that example, though, I'd rather just write a: let [ data, folder ] = await Promise.all([get_data, make_folder]);
and do the rest as simple "sequential-ish" code.I've written code with async.js, and it's nice. I vastly prefer Promises. The only thing I still use from async.js is the queueing stuff, and I wrap that up with Promises.
async function Promise_auto(clauses) {
let keys = Object.keys(clauses);
let results = { };
let clausePromises = { };
function runClause(key) {
if (!clausePromises[key]) {
clausePromises[key] = (async () => {
let fn = clauses[key];
if (fn instanceof Array) {
let deps = fn.slice(0, -1);
fn = fn.slice(-1)[0];
await Promise.all(deps.map(runClause));
}
console.log('start', key);
results[key] = await fn(results);
console.log('end', key);
})();
}
return clausePromises[key];
}
await Promise.all(keys.map(runClause));
return results;
}
With the above (note clauses are intentionally out-of-order): (async () => {
let results = await Promise_auto({
write_file: ['get_data', 'make_folder', async function(results){
console.log('in write_file', results);
return 'filename';
}],
email_link: ['write_file', async function(results){
console.log('in email_link', results);
return {'file':results.write_file, 'email':'user@example.com'};
}],
get_data: async function(){
console.log('in get_data');
return [ 'data', 'converted to array' ];
},
make_folder: async function(){
console.log('in make_folder');
return 'folder';
},
});
console.log('results = ', results);
})();
=>
start get_data
in get_data
start make_folder
in make_folder
end get_data
end make_folder
start write_file
in write_file { get_data: [ 'data', 'converted to array' ],
make_folder: 'folder' }
end write_file
start email_link
in email_link { get_data: [ 'data', 'converted to array' ],
make_folder: 'folder',
write_file: 'filename' }
end email_link
results = { get_data: [ 'data', 'converted to array' ],
make_folder: 'folder',
write_file: 'filename',
email_link: { file: 'filename', email: 'user@example.com' } }
Note that there was no topo-sorting or any other stuff to explicitly figure out an execution order, it just takes advantage of the fact that Promises can have their values pulled out multiple times to sequence things properly.