Promise.all(): Return a result after all Promises

2019-09-15 17:08发布

问题:

I am having trouble with this code:

// var env_array = ["env1", "env2", "env3", "env4"];

Promise.all(env_array.map(function(env) {
    return device_get_env(env).catch(function(err) { return err });
})).then(function(data) {
    console.log(data);
    data.forEach(function(entry) {
        console.log(entry.data.connected);
    });
}).catch(function(data) {
    console.log(data);
});


function device_get_env(env) {
    var env = ...;
    var device_id = ...;

    return get_token_data(env, 0).then(function(data) {

        var url_base = ... ;

        return $.ajax({
            url: url_base,
            method: "GET",
            dataType: 'json',
            headers: {Authorization: data.token_type + " " + data.access_token}
        });
    });
}


function get_token_data(env, auth) {
    var client_id = env_tokens[env].client_id;
    var client_secret = env_tokens[env].client_secret;
    var audience = auth == 1 ? "https://" + env + ".xxxx.com/api/v2/" : "yyyy.com";

    return $.ajax({
        url: "https://" + env + ".xxxx.com/oauth/token",
        method: "POST",
        data: {
            "client_id": client_id,
            "client_secret": client_secret,
            "audience": audience,
            "grant_type": "client_credentials"
        },
        dataType: 'json'
    });
}

Basically I need to iterate over env_array and find device items in some of my environments.

device_get_env() returns AJAX call, which could be a success/200 or error/404.

Thus my Promises.all won't return unless all promises are resolved.

I've been digging how to overcome this.

Been trying to implement this solution: https://stackoverflow.com/a/30378082/1913289, but I'm having this error here:

TypeError: device_get_env(env).catch is not a function. (In 'device_get_env(env).catch(function(err) {return err} )', 'device_get_env(env).catch' is undefined)

Any way to solve this with my code here?

UPD: implementation suggested by @Bergi

function get_token_data(env, auth) {
    var client_id = env_tokens[env].client_id;
    var client_secret = env_tokens[env].client_secret;
    var audience = auth == 1 ? "https://" + env + ".xxxx.com/api/v2/" : "yyyy.com";

    return Promise.resolve(
        $.ajax({
            url: "https://" + env + ".xxxx.com/oauth/token",
            method: "POST",
            data: {
                "client_id": client_id,
                "client_secret": client_secret,
                "audience": audience,
                "grant_type": "client_credentials"
            },
            dataType: 'json'
        })
    )
}


function device_get_env(env) {
    var env = ...;
    var device_id = ...;

    return get_token_data(env, 0).then(function(data) {
        var url_base = ... ;

        return Promise.resolve(
            $.ajax({
                url: url_base,
                method: "GET",
                dataType: 'json',
                headers: { Authorization: data.token_type + " " + data.access_token }
            })
        )
    });
}


Promise.all(env_array.map(function(env) {
    return device_get_env(env).then(null, function(err) { return err });
})).then(function(data) {
    console.log(data);
}).catch(function(data) {
    console.log(data);
});

UPD1:

Promise.all(env_array.map(function(env) {
    return device_get_env(env).catch(function(err) {return err} );
})).then(function(data) {
    console.log(data);
}).catch(function(data) {
    console.log(data);
});

FINAL UPD: I used fetch: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Supplying_request_options

to replace my AJAX http requests.

The Promise returned from fetch() won’t reject on HTTP error status even > if the response is an HTTP 404 or 500. Instead, it will resolve normally > (with ok status set to false), and it will only reject on network failure > or if anything prevented the request from completing.

Promise.all example:

Promise.all(env_array.map(function(env) {
    return device_get_env(env);
})).then(function(data) {
    console.log(data);
}).catch(function(data) {
    console.log(data);
});

fetch example:

function get_token_data(env, auth) {
    var client_id = env_tokens[tenant].client_id;
    var client_secret = env_tokens[env].client_secret;
    var audience = auth == 1 ? "https://" + env + ".xxxx.com/api/v2/" : "yyyy.com";

    var url_base = "https://" + env + ".xxxx.com/oauth/token";
    var myInit = {
        method: 'POST',
        mode: 'cors',
        dataType: 'json',
        cache: 'default',
        data: {
            "client_id": client_id,
            "client_secret": client_secret,
            "audience": audience,
            "grant_type": "client_credentials"
        }
    };

    return fetch(url_base, myInit);
}

回答1:

Your first clue that you aren't returning a Promise somewhere in your code is in your error:

TypeError: device_get_env(env).catch is not a function.

The error is telling you that you are trying to call .catch() on an object that doesn't have a catch() method on it (even though AJAX objects are technically "thenable", earlier versions of jQuery don't have a catch() method. So one solution is to upgrade your version of jQuery1.

Your particular problem is that you are not getting the ES6 Promise you expect from the AJAX call in your get_token_data() function, instead, you are returning a jQuery Promise.

So if you wrap/cast the jQuery promise to a catch()-able ES6 Promise via Promise.resolve() in get_token_data() then you should be on the right track:

function get_token_data(env, auth) {
  var client_id = env_tokens[env].client_id;
  var client_secret = env_tokens[env].client_secret;
  var audience = auth == 1 ? "https://" + env + ".xxxx.com/api/v2/" : "yyyy.com";

  var ajaxOptions = {
    url: "https://" + env + ".xxxx.com/oauth/token",
    method: "POST",
    dataType: 'json',
    data: {
      "client_id": client_id,
      "client_secret": client_secret,
      "audience": audience,
      "grant_type": "client_credentials"
    }
  };

  // here is where the magic happens to cast your 
  // jQuery Promise into a 'catchable' Promise 
  return Promise.resolve($.ajax(ajaxOptions));
};

I can't test this code, so you might need to tweak it, but that is the basic idea.

Hope this helps!

1 Thanks to @Bergi for reminding me of this.



回答2:

Your problem is that device_get_env(env) returns a jQuery promise, not an ES6 promise. And those don't have a .catch() method until v3.0…

To overcome this issue, you can either

  • update jQuery,
  • dodge the jQuery promise by doing return Promise.resolve($.ajax(…)) in get_token_data, or
  • use .then(null, function(err) { return err }) instead of .catch(…)