4 ms·
OK, I'll shoot: Why not Promises? They don't handle streaming data, but they're not supposed to; they're supposed to model the traditional JS function call mo
by phs2501 11y ago
OK, I'll shoot: Why not Promises?
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 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#cancellation 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!)
- jameslk 11y agoNot the op, but I can give you one important reason for me why Promises aren't as great when compared to callbacks (specifically wrt the ES7 async/await syntax): with callbacks, I have greater flexibility in choosing the best approach for running asynchronous code in parallel. 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.
- phs2501 11y agoPromises are just as easy to parallelize as callbacks, though. Easier, IMO, since you can pull their values more than once. 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 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.
- phs2501 11y agoAs an example of non-trivial parallelism with Promises and async/await, here's a (slightly naive) implementation of Promise.auto I just whipped up. 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.