1

I have 2 arrays with email address that I want to feed to the function below, one after the other (not in parallel) and then perform a final function.

I've been looking at examples trying to figure out the correct way to chain these, but I think I'm now more confused than when I started. Tried playing with Promise.all to ensure steps completed before going to next but that was a disaster.

eg: (do array 1).then(do array 2).then(final function)

EmailArray.forEach(fGetUserId(X)) and push to IdArray

function fGetUserId(X) {

        $.ajax({ // get the Ids of users, return to populate IdArray
            url: xhost + "/portal/_api/web/SiteUsers?$select=Id&$filter=Email eq '" + X + "'",
            type: "GET", headers: { "Accept": "application/json;odata=verbose", }, //verbose
            success: function (data) {
                var xuserid = data.d.results[0].Id;
                return xuserid;
            },
            error: function (error) { alert(JSON.stringify(error)); }
        });    
    }

Help?

2
  • Can I not use $.when.apply something something?
    – BDC604
    Commented May 23, 2019 at 17:54
  • I'm not familiar with the syntax of JQuery, but I believe you can't use async await, .then(), .catch(), nor Promise.all(), because those are specific to native promises, not JQuery's promise-like syntax.
    – Cardin
    Commented May 26, 2019 at 3:49

3 Answers 3

2

JQuery AJAX does not use native JS promises. You can observe this by the way they defined a success and error callback.

You can explore using fetch https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API, which is a reasonable browser API since you are already using Promise. I'll provide examples for both fetch and ajax:

/**
  @returns {Promise.<number|void>}
*/
async function fGetUserId(x) {
  /** @type {Promise.<any>} */
  let fetchPromise = fetch(xhost + "/portal/_api/web/SiteUsers?$select=Id&$filter=Email eq '" + X + "'", {
      method: "GET",
      headers: {
        Accept: "application/json;odata=verbose"
      }
    }
  ).then(x => x.json()); // convert response to JSON, aka Promise.<Response> to Promise.<any>

  let idPromise = fetchPromise
    .then(data => {
      var xuserid = data.d.results[0].Id;
      return xuserid;
    }) // Promise.<any> to Promise.<number>
    .catch(err => alert(JSON.stringify(err))); // in case of error, it becomes Promise.<void> instead, since we do not return anything within the catch()
  let userid = await idPromise; // Promise.<number|void> to number|void.
  return userid; // number (but actually Promise.<number|void> since fGetUserId has the async modifier in its signature)
}
/**
  @returns {Promise.<number|void>}
*/
async function fGetUserId(x) {
  /** @type {Promise.<number|void>} */
  let myPromise = new Promise((resolve, reject) => {
    $.ajax({
      url: xhost + "/portal/_api/web/SiteUsers?$select=Id&$filter=Email eq '" + X + "'",
      type: "GET",
      headers: { Accept: "application/json;odata=verbose", }, //verbose
      success: function (data) {
        var xuserid = data.d.results[0].Id;
        resolve(xuserid); // signify the promise succeeded with xuserid as return value
      },
      error: function (error) {
        alert(JSON.stringify(error));
        reject(); // signify the promise faulted with no return value
      }
    });
  });
  let userid = await myPromise; // Promise.<number|void> to number|void.
  return userid; // number (but actually Promise.<number|void> since fGetUserId has the async modifier in its signature)
}

There might be slight errors as I didn't test the code, but I hope the gist is still conveyed!

To call this code, you run this:

/** @type {string[]} */
let EmailArr = ["..", "..", ".."];

/** @type{(Promise.<number|void>)[]} */
let promiseArr = EmailArr.map(x => fGetUserId(x)); // map an array of emails to an array of promises

Promise.all(promiseArr) // wait for array of promises to resolve into array of userid, (aka Promise.<number|void>)[] to (number|void)[]
  .then(userids => console.log("done!", userids)
  .catch(err => console.error(err));

Note that Promise.all() itself is a Promise. You can use .then() to handle it as I did, or use await. Note that await can only be used in an async function, so you might want to declare a global anonymous async function:

(async () => {
  // You can use "await" here
})();
2
  • It grabs the xuserid fine, but I don't see how this is populating an array of Id's. Uncaught (in promise) TypeError: Cannot read property 'catch' of undefined.
    – BDC604
    Commented May 24, 2019 at 17:09
  • I've added some JSDoc/TypeScript annotations to help follow the flow. Btw did the error occur in fGetUserId? Which line is it?
    – Cardin
    Commented May 26, 2019 at 4:05
0

I'll throw my hat in since i ran into the same problem, i wanted to execute a series of fetch calls on an array IN ORDER (not all at once). So i came up with this (including the entire ajax helper i created and used, but the bottom method is the main thing:

export class AjaxHelper {

    constructor() {

    }

    /*
     * Performs a POST on the given url, passing the given data (if any) as the Body post
     * @param Url - Url of the API to hit
     * @param Data - The JSON object the post as the body parameters
     * @returns response parsed as T Type
    */
    async postRequest<T>(Url: string, Data?: any): Promise<T> {
        const response = await fetch(Url, {
            method: "POST",
            mode: 'cors',
            cache: "no-cache",
            headers: {
                'Content-Type': 'application/json'
            },
            body: (Data ? JSON.stringify(Data) : "")
        });
        return await response.json();
    }

     /*
     * Performs a GET on the given url, passing any data records as query parameters, and parsing the response as T object type
     * @param Url - Url of the API to hit
     * @param Data - Key (string) value (string, number, or boolean) of any parameters to include in the call, these are parsed as URL variables    
     * @returns response parsed as T Type
    */
    async getRequest<T>(Url: string, Data?: Record<string, (string | number | boolean)>): Promise<T> {
        var urlWithParams = Url;
        if (Data) {
            let params = new Array<string>();
            for(let key in Data) {
                params.push(encodeURI(key)+"="+encodeURI(Data[key].toString()))
            }
            urlWithParams += "?" + params.join("&");
        }
        const response = await fetch(urlWithParams, {
            method: "GET",
            mode: 'cors',
            cache: "no-cache"
        });
        return await response.json();
    }

    /*
     * Runs the given promise calls in order against the provided array of values, waiting for each item to finish it's promise before executing the next
     * @param values - Array of values that will execute in the funcToRun in order
     * @param funcToRun - Function that takes the single value and returns a Promise<TPromise> of the asynchronous call (ex postRequest or getRequest of this helper)
     * @param funcAfterEachRun - Optional function that receives the Promise's result, if you return false then aborts the rest of the calls, nothing / true continues
     * @param funcWhenFinished - Optional function that runs once every item is executed.
    */
    public runInSequence<T, TPromise>(values : Array<T>, funcToRun : (value : T) => Promise<TPromise>, funcAfterEachRun? : (value : TPromise) => boolean | any | undefined, funcWhenFinished? : () => any | undefined) : void {
        if(values.length > 0) {
            var value = values[0];
            values = values.splice(1);
            funcToRun(value).then((tPromise : TPromise) => {
                if(typeof funcAfterEachRun !== undefined && funcAfterEachRun != null){
                    var result = funcAfterEachRun(tPromise);
                    if(typeof result === "boolean" && !(result as boolean)) {
                        // Abort
                        return;
                    }
                }
                this.runInSequence(values, funcToRun, funcAfterEachRun, funcWhenFinished);
            });
        } else {
            if(typeof funcWhenFinished !== undefined){
                funcWhenFinished!!();
            }
        }
        return;
    }
}

Usage is below, the input (string) calls this.theMethodThatReturnsPromiseOfFooResponseObj(value: string) : Promise<FooResponseObj>()


ajax.runInSequence<string, FooResponseObj>(arrayOfStrings, 
        (value : string) => {
            return this.theMethodThatReturnsPromiseOfFooResponseObj(value);
        },
        (value : FooResponseObj) => {
            if(value.successful){
                console.log("One Complete");
            } else {
                console.log("Failed, aborting");
                return false;
            }
        },
        () => {
            // Finished function
            console.log("All tasks complete");
        }
        );

You would have to translate it to basic JavaScript if you wish to do that, I work primarily in typescript myself.

0

Since @TravorF is resurrecting this question, I can't help but throw in another option.

I know that the OP is clearly using jQuery, but this answer takes a more modern approach to the general question of how to do async operations (not necessarily using $.ajax) in sequence. If you're able to use the async/await keywords, there's a much simpler approach than @TrevorF's complicated recursive approach.

It becomes very simple once you understand that the for...of loop construction respects await.

That fact means that you can construct loops that will wait for async operations to complete before continuing on to the next iteration.

So processing multiple async operations in sequence can be done like this:

const processMultipleAsyncActionsInSequence = async (allTheThings) => {
    const allResults = [];
    try {
        // the "for...of" loop respects "await", so this executes
        // exactly as it looks like it should, with the loop
        // waiting for the "await" to complete before moving on
        for (const oneThing of allTheThings) {
            const singleResult = await processSingleAsyncRequest(oneThing);
            allResults.push(singleResult);
        }
    } catch (err) {
        console.warn(err);
        throw err;
    }
    return allResults;
}

const processSingleAsyncRequest = async (singleThing) => {
    let result = null;
    try {
        // do whatever you need to with your individual "singleThing" to construct your query.
        // in this case we'll just do a simple SharePoint get item by ID.
        const url = `<sharepoint site>/_api/web/lists/getbytitle('List Name')/items(${singleThing.ID})`;
        const response = await fetch(url, {
            method: 'GET',
            headers: {
                'accept': 'application/json'
            }
        });
        const responseJSON = await response.json();
        // do whatever you need to do here to transform the raw JSON
        // into the result you want to send back to the calling function,
        // for instance if you used a $filter and you want to pull the results
        // out of response.value, etc.
        //
        // in this particular case we probably don't need to do that
        result = responseJSON;
    } catch (err) {
        throw err;
    }
    return result;
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.