Async: Promise support

Created on 13 Nov 2015  ·  22Comments  ·  Source: caolan/async

Add promise support to allow mix different tasks:

async.waterfall([
    function (done) {
        // Do something and call done()
        fs.readFile(filepath, done);
    },
    function(content) {
        // Do something and return Promise
        return mongoose.models.file.create({
            name: filepath,
            content: content
        });
    }
], (err) => {
    if (err) {
        console.error(err);
    }
});
feature

Most helpful comment

I think just like there is async.asyncify there could be a async.promisify function.

Then I can just await async.promisify(async.mapLimit(x,10, mapper))

All 22 comments

asyncify will take a synchronous function that returns a Promise and call a callback on its resolved/rejected handlers:

async.waterfall([
    function (done) {
        // Do something and call done()
        fs.readFile(filepath, done);
    },
    async.asyncify(function(content) {
        // Do something and return Promise
        return mongoose.models.file.create({
            name: filepath,
            content: content
        });
    })
], (err, model) => {
    if (err) {
        console.error(err);
    }
});

But that feature isn't documented...

I also had another thought -- should we automatically asyncify functions that return promises (or handle them appropriately?). What should async do when passed an ES-whatever async function (that implicitly returns a promise)?

e.g.

async.waterfall([
    function (done) {
        // Do something and call done()
        fs.readFile(filepath, done);
    },
    async function(content) {
        return await mongoose.models.file.create({
            name: filepath,
            content: content
        });
    }
], (err, model) => {
    if (err) {
        console.error(err);
    }
});

Personally, I think async should asyncify promises by default. There are some async functions that I write that I also have to feed into async.queue, but I don't want to write this:

import {queue, asyncify} from 'async'

const run = asyncify(async function () {
  await someStuff()
})

const q = async.queue(run)
q.push('asdf')

where I could be writing this

import {queue} from 'async'

async function run () {
  await someStuff()
}

const q = async.queue(run)
q.push('asdf')

Added the docs for asyncify. Going to keep this open for the automatic asyncify-ing behavior.

About this, I was experimenting with use the same codebase and using a Promise interface for methods that use callback as last parameter. check it !

async.waterfall([
  function (callback) {
    callback(null, 'one', 'two')
  },
  function (arg1, arg2, callback) {
    // arg1 now equals 'one' and arg2 now equals 'two'
    callback(null, 'three')
  },
  function (arg1, callback) {
    // arg1 now equals 'three'
    callback(null, 'done')
  }
]).then(function (value) {
  console.log(value === 'done')
})

what do you think about it? I think that could be easy to adapt the library. See for example how works got library handle cb and promise.

Very interesting. If Async properly handled functions that return promises, then it could also accept promisify-ed Async functions pretty easily. This would work:

async.parallel([
  async.waterfall([
    asyncFn1,
    function (result1, next) {
      //...
    }
    asyncFn2,
    //...
  ]), // no callback!
  async.each(arr, function (item, cb) {
    //...
  })
  otherAsyncFn
], done)

It would be much easier to combine Async functions.

The problem today is that callbacks are optional. Leaving off the last argument still executes the task. I would love to make is so Async functions are automatically curried -- if you leave off the callback, it partially applies the function, and all you need to do is call it with a callback. But seeing as many methods have optional parameters, and we're moving towards optional callbacks across the board, you cant really do it. Our methods are variadic, and you cant curry variadic functions.

However, if leaving off the final callback off a method has it return a Promise, and if Async is set up to handle functions that return promises, is an interesting combo. One major issue is then Async would have to integrate with a promise library -- which one? Do we rely on global.Promise, forcing older engines to polyfill, or do we use something like Bluebird? Also, Async is kind of a statement against promises -- higher-order functions for callback-accepting functions, rather than adopting the Futures paradigm. I'm all for interoperability with Promises, but I think having Async return promises is a bit outside its design philosophy.

@aearly

I feel that Promises is a workflow align with the last node versions (>=4). In this versions Promises are available, so, my vision is use Promise workflow when the global environment have Promises.

It's possible add a tiny polyfill (check pinkie-promise) but in my opinion don't have sense provide a polyfill. Better force the user upgrade the node version for use the last node features. Really, check (got 6 PR)[https://github.com/sindresorhus/got/pull/140]. Dependencies are not welcome in very tiny project and are used in everywhere.

Maybe this feature could be nice to be included after async modularization, that the codebase will be more easy to adapt.

But definetly, align async with promises is a totally _must_!

@Kikobeats :+1:

Promises are becoming the first class async functions. So it's hard to imagine the must-heave library for asynchrony without full support of them.

I think it could be implemented without polyfill but with feature detection: allow if there is a global Promise object with resolve method.

@aearly bluebird adds browser compatibility to the list of it polyfilling. I think it might be appropriate to use it.

@martinheidegger Bluebird is too big for this. It will cast 76 Kb (minified) for simple support of promise methods.

Again, use callback or promises interface in your backend workflow is a natural process based in your node version.

  • If you have a more time backend (2 years or more, maybe), you use use 0.10 or 0.12 version, so, your code is written in callback style.
  • If you backend have less than ~6 months and you use >=4 node version probably you use a Promise style.

But In any case you not have a mixed style.

So is not necessary add a dependency for support Promises; If async supports promises one day in the future, you can use it if your node version supports Promises.

Extreme case: _I'm using a a older node version but my backend is written using promises style_. Then you have have defined a Promises object as global or define one before use async. Whatever you want. Simply.

Same behavior for the frontend side. Not dependency is necessary.

Hmmn, it seems the same problems Promises had 4-5 years ago still apply. If everyone was using node 4 or later, and modern browsers with ES6 support, I we could easily utilize promises internally where applicable.

The problem is we leave our node 0.12 and older browser users in the dust, requiring them to polyfill. Which polyfill do they use? We wouldn't want to bundle one, even pinkie-promise has weight to it. Also, people who are using promises outside of the standard ES6 Promise will want to use bluebird or q etc., whatever it is that they're using. I don't want to require the use of a polyfill -- npm i -S async should be all people need to do.

Async has also been somewhat of a reaction against Promises -- an alternative way to manage asynchronous code. To start using Promises seems like a step back from that. Properly managed callbacks can be just as powerful as promises (and in many cases, faster, since Promises have a built-in event-loop deferral).

I'm all for interop with promises, if a function is passed a Promise or other "thennable" than we should definitely take advantage of it. But I think we should avoid creating and/or returning Promises internally, just due to the fact ES6 support across platforms still has a way to go.

Agreed, promises aren't cheap to create or process. I'd rather see something like this as an opt in extension. Promises are great but you won't always want them

IMHO, after migrating a lot of node's code to promises + co + yield, i did not found direct replacement only for .eachLimit(). Probably, the list of useful cases is much less than looks at first glance, and can be handled with separate package.

I like @Kikobeats' little extension, it may make sense to recommend it in the readme (/cc @aearly).

I would be strongly against official support of promises in async for reasons brought up by myself and other users.

@megawac What about the case where a function returns a promise? e.g. @SEAPUNK 's example.

@megawac I Added support for callback style,tests and browser build in promise-async. Then if anybody want to use Promise, I think that is ready :+1:

@aearly, that'd be fine I suppose (so =) but I'd rather leave it in plugin
land personally

On Fri, Jan 22, 2016 at 2:17 PM, Kiko Beats [email protected]
wrote:

@megawac https://github.com/megawac I Added support for callback style
and tests for promise-async
https://github.com/Kikobeats/promise-async#promise-async. Then if
anybody want to use Promise, I think that is ready [image: :+1:]


Reply to this email directly or view it on GitHub
https://github.com/caolan/async/issues/956#issuecomment-174016628.

Here's another package that promisifies all the available async methods:
https://github.com/eladnava/koa-async

I think just like there is async.asyncify there could be a async.promisify function.

Then I can just await async.promisify(async.mapLimit(x,10, mapper))

I wouldn't object to that if you want to create a pull request

On Sat, Dec 17, 2016 at 4:57 PM, Manoj Patel notifications@github.com
wrote:

I think just like there is async.asyncify there could be a async.promisify
function.

Then I can just await async.promisify(async.mapLimit(x,10, mapper))


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/caolan/async/issues/956#issuecomment-267789633, or mute
the thread
https://github.com/notifications/unsubscribe-auth/ADUIEKJIDulPHAn_SeEZbiPb3t7ORGnpks5rJFqvgaJpZM4Gh1fr
.

I'm using bluebird module's promisifyAll method to convert callbacks into promises so that async's methods become:

// adds '<original-method>Async' to class 
Promise.promisifyAll(async);

function somePromise() {
    return async.parallelAsync([
        function(cb) {
            setTimeout(function(){
                cb(new Error('err'), 'foo')
            }, 1000);
        },
        function(cb) {
            setTimeout(function(){
                cb(null, 'bar')
            }, 1000);
        }
    ]);
}

somePromise().then(function(result){
    console.log('result',result);
}).catch(function(err){
    console.log('err',err);
});

JSFiddle Example

Was this page helpful?
0 / 5 - 0 ratings