Search code examples
office-jsscriptlab

Await a asynchronous function return a range object


I'd like to use a function to return a range object, but because the function is asynchronous, when my code reaches currentRangeSelection.select();, it is still null. How do I force the code to await the function getCurrentRangeSelection so I can get the object instead undefined?


EDIT 1 - Rick Kirkham: I've simplified the code so now it is possible just to paste it in the default Basic API Call (JavaScript) template in ScriptLab to test it.

After studying Promises for hours, I think I'm close to what I want, but it still doesn't work:

$("#run").click(run);

function run() {
    var currentRangeSelection;

    return Excel.run(function (context) {
        return context.sync()
            .then(function () {
                return getCurrentRangeSelection(currentRangeSelection)
            })
            .then(function () {
                // this returns an error because currentRangeSelection is undefined:
                console.log("Outside the function: " + currentRangeSelection.address);
                currentRangeSelection.select();
                return context.sync();
            })
    })
        .catch(function (error) {
            console.log(error);
        });
}

function getCurrentRangeSelection(rangeSelection) {
    return Excel.run(function (context) {
        return context.sync()
            .then(function () {
                rangeSelection = context.workbook.getSelectedRange();
                rangeSelection.load();
                return context.sync();
            })
            .then(function () {
                // this works:
                console.log("Inside the function: " + rangeSelection.address);
                return context.sync();
            });
    });
}

As you can see, now the function returns a Promise because it is (I think) the return value of Excel.run function, and now I pass the desired return range as a parameter, but still doesn't work. Any more tips?


EDIT 2 - Rick Kirkham

(1) I tried to create a function that returns a Promise, so I could insert it in the caller then chain. Because of that, my only choice was to pass as reference the parameter that returns the range.

(2) Yes, I agree with you. I think (1) explained why I didn't return a range object in the first place.

(3) Yes, I fixed that.

(4) The original function was more complex and a single method from Office-JS API wouldn't return what I really wanted. I solved my problem by doing this:

$("#run").click(run);

function run() {
    var testRange;

    return Excel.run(function (context) {
        return context.sync()
            .then(function () {
                testRange = getCurrentRangeSelection(context);
                testRange.load();
                return context.sync();
            })
            .then(function () {
                console.log(testRange.address); // this works
                return context.sync();
            });
    }).catch(function (error) {
        console.log(error);
    });
}


function getCurrentRangeSelection(context) {
    var rangeSelection = context.workbook.getSelectedRange();
    var activeWorksheet = context.workbook.worksheets.getActiveWorksheet();
    var usedRange = activeWorksheet.getUsedRange();
    return usedRange.getIntersection(rangeSelection);
}

My next question you answered with your edit: "What if I wanted the function getCurrentRangeSelection to return an already loaded object?"

Thanks for the example and the answers!


Solution

  • Try this: Redesign the getCurrentRangeSelection function to return a Promise object. Then call the then method of the promise object to call currentRangeSelection.select()

    Edit in light of the OP's comments below and rewritten question:

    Please go to this sample and see the getDocumentFilePath function in the home.js file. Structure your getCurrentRangeSelection method like it and pass the context object to it as a parameter. Then use the context object to get the currently selected range with the context.workbook.getSelectedRange() method. Then pass the thing you want to return (the currently selected range, I presume) as the parameter to the resolve method.