Why is my promise returning undefined?

2019-01-27 01:53发布

问题:

Background

I am trying to make a function that delays the execution of asynchronous functions by X ms.

For the purposes of this demonstration, the following is the async function, which takes an URL:

/*
 *  This is a simulation of an async function. Be imaginative! 
 */
let asyncMock = function(url) {
    return new Promise(fulfil => {

        setTimeout(() => {
            fulfil({
                url,
                data: "banana"
            });
        }, 10000);

    });
};

Objective

My objective here is to have a function, that will take the url argument of asyncMock and then call it every X ms or until there are no more arguments left.

Basically, I want every invocation of asyncMock to be separated by X ms.

As an example, imagine I call asyncMock 20 times in a row. Normally, those 20 calls would be done immediately. What I want, it to make sure that there is Xms of delay between each of the 20 calls.

Tentative

My idea to solve this, is to have a factory, that will return a promise that will execute the function after X ms.

let throttleFactory = function(args) {

    let {
        throttleMs
    } = args;

    let promise = Promise.resolve();

    let throttleAsync = function(url) {

        return promise.then(() => {

            setTimeout(anUrl => {
                return new Promise( fulfil => {
                    fulfil(asyncMock(anUrl));
                });
            }, throttleMs, url);
        });
    };

    return Object.freeze({
        throttleAsync
    });
};

Ideally I would use this factory like in the example bellow:

let throttleFuns = throttleFactory({
    throttleMs: 2000
});

console.log('running');

throttleFuns.throttleAsync('http://www.bananas.pt')
    .then(console.log)
    .catch(console.error);

throttleFuns.throttleAsync('http://www.fruits.es')
    .then(console.log)
    .catch(console.error);

throttleFuns.throttleAsync('http://www.veggies.com')
    .then(console.log)
    .catch(console.error);
// a ton of other calls in random places in code

Problem

The problem here is that my throttleAsync fucntion outputs undefined three times immediately. I believe this might be because I am not defining promise properly.

Question

How can I fix this code to work as intended?

回答1:

Because throttleAsync returns the result of calling promise.then, and the then callback doesn't return anything. This makes the promise created by then resolve with the value undefined.

You probably meant to have it return the new promise you're creating, but you're not doing that until the setTimeout callback. You want to do it prior (but there's more, keep reading):

let throttleAsync = function(url) {

    return promise.then(() => {
        return new Promise( fulfil => {
            setTimeout(anUrl => {
                fulfil(asyncMock(anUrl));
            }, throttleMs, url);
        });
    });
};

There's also no reason to pass the URL through setTimeout like that, so:

let throttleAsync = function(url) {

    return promise.then(() => {
        return new Promise( fulfil => {
            setTimeout(() => {
                fulfil(asyncMock(url));
            }, throttleMs);
        });
    });
};

Originally I though the promise there was unnecessary, but you've clarified that you want to ensure that repeated calls are "spaced out" by throttleMs. To do that, we'd use the above, but update promise:

let throttleAsync = function(url) {

    return promise = promise.then(() => {
    //     ^^^^^^^^^
        return new Promise( fulfil => {
            setTimeout(() => {
                fulfil(asyncMock(url));
            }, throttleMs);
        });
    });
};

This way, the next call to asyncThrottle will wait until the previous one has fired before starting the next.

Live Example:

const throttleMs = 1000;

const asyncMock = url => url;

let promise = Promise.resolve();

let throttleAsync = function(url) {

    return promise = promise.then(() => {
    //     ^^^^^^^^^
        return new Promise( fulfil => {
            setTimeout(() => {
                fulfil(asyncMock(url));
            }, throttleMs);
        });
    });
};

console.log('running');

throttleAsync('http://www.bananas.pt')
    .then(console.log)
    .catch(console.error);

throttleAsync('http://www.fruits.es')
    .then(console.log)
    .catch(console.error);

throttleAsync('http://www.veggies.com')
    .then(console.log)
    .catch(console.error);



回答2:

Here's your problem:

        setTimeout(anUrl => {
            return new Promise( fulfil => {
                fulfil(asyncMock(anUrl));
            });
        }, throttleMs, url);

What you're doing here is you return a promise from the setTimeout callback. The return value of functions run by setTimeout are ignored so no one will get that value.