Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert JavaScript functions that use multiple callbacks, to use promises?

I am trying to convert the way I use asynchronous functions from callbacks to promises.

I understand this basic conversion shown here, where callbacks are converted to the resolve and reject functions:

// CALLBACK
const getData = (id, callback) => {
    setTimeout(() => {
        if (!id) return callback('ERROR: id is missing')
        return callback("The data for id " + id);
    }, 1000)
};

getData(111, console.log);
getData(222, console.log);
getData(null, console.log);

// PROMISE
const getData2 = id => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (!id) reject('id is missing');
            resolve("The data for id " + id);
        }, 1000);
    });
};
getData2(333).then(console.log).catch((message) => console.log("ERROR: " + message));
getData2(444).then(console.log).catch((message) => console.log("ERROR: " + message));
getData2(null).then(console.log).catch((message) => console.log("ERROR: " + message));

But I often use callbacks as in the following scenerio where you can have a process which takes a long time and sends out bits of data in numerous callbacks back to the calling code as it processes its information:

sleep = function (ms) {
    var start = new Date().getTime();
    let now = 0;
    let difference = 0;
    for (var i = 0; i < 1e17; i++) {
        now = new Date().getTime();
        difference = now - start;
        if (difference > ms) {
            break;
        }
    }
}

const goShopping = (list, cbItemReport, cbFinished, cbError) => {
    let count = 0;
    let numberFound = 0;

    const randomError = Math.floor(Math.random() * 3);
    if (randomError == 0) {
        cbError('Something went wrong, trip aborted.');
    } else {
        list.forEach(item => {
            const randomFound = Math.floor(Math.random() * 4);
            if (randomFound > 0) {
                cbItemReport(item, true, ++count);
                numberFound++;
            } else {
                cbItemReport(item, false, ++count);
            }
            sleep(1000);
        })
        cbFinished(`Bought ${numberFound} things.`);
    }
}

goShopping(['milk', 'eggs', 'sugar', 'bread'],
    (item, found, count) => {
        console.log(`Item #${count} "${item}" was ${found ? 'found' : 'not found'}.`);
    },
    (message) => {
        console.log("Returned from shopping: " + message);
    },
    (error) => {
        console.log("ERROR: " + error);
    });

How would I convert this latter use of callbacks to promises? In this case, the three callbacks cbItemReport, cbFinished, cbError are too many to map to the two that Promise has, i.e. only resolve (cbFinished) and reject (cbError), or what am I missing here?

like image 778
Edward Tanguay Avatar asked Mar 12 '26 12:03

Edward Tanguay


1 Answers

From what it sounds like, you are looking to implement something like the RxJs library, so why not just use it?

Check out RxJs here

For example a call could look like this then:

const sub = new Subject(); // Create a subject 

sub.asObservable().subscribe( // Subscribe to that subject as obserable
    ({item, found, count}) => { // next
        console.log(`Item #${count} "${item}" was ${found ? 'found' : 'not found'}.`);
    },
    error => { // error
        console.log("ERROR: " + error);
    },
    message => { // complete
        console.log("Returned from shopping: " + message);
    }
);

const goShopping = (list) => {
    let count = 0;
    let numberFound = 0;

    const randomError = Math.floor(Math.random() * 3);
    if (randomError == 0) {
        sub.error('Something went wrong, trip aborted.'); // push an error to the subject
    } else {
        list.forEach(item => {
            const randomFound = Math.floor(Math.random() * 4);
            if (randomFound > 0) {
                sub.next({item: item, found: true, count: ++count}); // push a result to subject (single object)
                numberFound++;
            } else {
                sub.next({item: item, found: true, count: ++count}); // same as above
            }
            sleep(1000);
        })
        sub.complete(`Bought ${numberFound} things.`); // push complete to subject. after that no next is allowed anymore
    }
}

goShopping(['milk', 'eggs', 'sugar', 'bread'])
like image 137
MauriceNino Avatar answered Mar 14 '26 00:03

MauriceNino