简体   繁体   中英

How do I write an asynchronous Map function in Javascript from scratch?

I need help writing this asyncMap function from scratch. I think I've almost got it, but I'm not sure why I keep getting the wrong answer. Here's the code I have so far:

function wait3For1(callback){
    setTimeout(function(){
        callback('one')
    }, 300)
}

function wait2For5(callback){
    setTimeout(function(){
        callback('five')
    }, 200)
}


function asyncMap(tasks, callback){
    return callback(
    tasks.map((item) => 
        item((element) => element)))
}

asyncMap([wait3For1, wait2For5], function(arr){
    console.log(arr) //expect ['one', 'five']
});

I keep getting [undefined, undefined] I'm pretty sure it's because I'm not doing the callbacks wait2For5 and wait3For1 correctly, but not sure what the problem is.

Thanks in advance!

The issue is that you're not waiting for the results to come back, collecting them, and then sending them back via the callback. See if this code helps. (It works when tested with your program.)

function asyncMap(tasks, callback) {
    // array to collect the results
    let results = [];

    // count of how many results we're waiting for
    let remaining = tasks.length;

    tasks.forEach((task, i) => {
        task((result) => {
            // Store the result in the right position.
            results[i] = result;

            // See how many results we're still waiting for.
            remaining -= 1;

            // If we're done, invoke the callback.
            if (remaining === 0) {
                callback(results);
            }
        });
    });
}

You are basically creating poor man's promise but without any error handling capability.

Try

  function waitFor(val, dur){ return new Promise(function(resolve, reject) { setTimeout(function() { resolve(val) }, dur); }); } Promise.all([waitFor('one',600), waitFor('five', 100)]).then( function(arr) { console.log(arr) //expect ['one', 'five'] }).catch(function(err){ console.log('ooops error:' , err) }); 

In your code, you're using the synchronous Array.prototype.map

function asyncMap(tasks, callback){
    return callback(
    tasks.map((item) => 
        item((element) => element)))
}

Since wait3For1 and wait2For5 have no return , they will implicitly return undefined which will be used in the result of the .map call. Obviously we will want to wait for the callback to be called before assigning the mapped value to the final result.

The other issue is that mapping over an array uses a functionmap(items) doesn't really make sense without a function to map the items with. So we'll address that in the solution below as well.

It will help if you start with asyncReduce and then implement asyncMap as an async reduce. Note, the code below will process the items in a series. If you wish for the items to be processed in parallel, a slightly different approach would be required. Let me know in the comments and I'll be happy to write up another variant.

 function wait3For1(callback){ setTimeout(function(){ callback('one') }, 300) } function wait2For5(callback){ setTimeout(function(){ callback('five') }, 200) } function asyncReduce(xs, f, initial, callback) { if (xs.length === 0) callback(null, initial) else f(initial, xs[0], function(x) { asyncReduce(xs.slice(1), f, x, callback) }) } function asyncMap(xs, f, callback) { asyncReduce(xs, function(acc, x, k) { f(x, function(y) { k(acc.concat([y])) }) }, [], callback) } asyncMap([wait3For1, wait2For5], function(f,callback) { f(callback) }, function(err, arr) { console.log(arr) //=> ['one', 'five'] }) 

Go ahead and run the code snippet to see it work


I see you using some arrow functions so you probably have a somewhat recent version of node. Here's the exact same thing but using ES6 instead

 // ES6 const wait3For1 = callback=> setTimeout(callback, 300, 'one') const wait2For5 = callback=> setTimeout(callback, 200, 'five') const asyncReduce = ([x,...xs], f, initial, callback) => x === undefined ? callback(null, initial) : f(initial, x, y=> asyncReduce(xs, f, y, callback)) const asyncMap = (xs, f, callback)=> asyncReduce(xs, (acc, x, k)=> f(x, y=> k([...acc, y])) , [], callback) asyncMap( [wait3For1, wait2For5], (f, callback)=> f(callback), (err, arr)=> console.log(arr) // ['one', 'five'] ) 


Crafting higher order functions from scratch is a very valuable exercise, but as another answer points out, you're just implementing a "poor man's Promise.all ", but much less versatile. You should convert wait3For1 and wait2For5 to Promise creators and use Promise.all instead.

Your next question might be how to implement Promise.all ... I've done this recent and found it to be quite a fun challenge. I hope some of the techniques shared in this answer will help should you choose to explore an implementation from scratch ^_^

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM