5 ms·
Callbacks suck, no doubt, and the sooner we have an real alternative (sorry Promises, not you) for them in JS, the better. In the meantime, for those of us stu
by rjrodger 11y ago
Callbacks suck, no doubt, and the sooner we have an real alternative (sorry Promises, not you) for them in JS, the better.
In the meantime, for those of us stuck with contemporary JS, there are mitigating code styles, in particular "early return":
function foo(a, callback) {
bar(a, function(err, b) {
if (err) return callback(err)
baz(b, function(err, c) {
if (err) return callback(err)
// some more stuff
callback(null, d)
})
})
}
Just to be clear, this is still inferior to a linear style.
It is however much clearer than the "traditional" style.
- phs2501 11y agoOK, 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.