todesengel
todesengel

Reputation: 23

How to return resolving Promise Like object from for loop

I have a promise like object in my own function that decrypts the given message using web crypto api. Problem is, in my decrypt function, I want to try couple of different values as an input and run this promise like object multiple times in a for loop and at the end function should return the object which ever of these promises resolve successfully.

public decryptMessage(latitude: [string, number], longitude: [string, number], ciphertext: string) {
    //get salt 
    const salt = localStorage.getItem("salt")
    const retrievedSaltArray = JSON.parse(salt)
    const saltBytes = new Uint8Array(retrievedSaltArray)

    //get iv
    const iv = localStorage.getItem("iv")
    const retrievedIvArray = JSON.parse(iv)
    const ivBytes = new Uint8Array(retrievedIvArray)

    //get tolerance distance
    let toleranceDistance = parseInt(JSON.parse(localStorage.getItem("toleranceDistance")))

    //get original keyHash
    let originalHash = localStorage.getItem("keyhash")

    //create location inputs(locations with adjacent quadrants)
    let location = new Location(latitude, longitude)
    let locationInputs = location.prepareReceiverLocationInputs()

    let encryptionTool = new EncryptionHelper(saltBytes, ivBytes)

    for (let i = 0; i <= locationInputs.length - 1; i++) {
        let plaintText = encryptionTool.decrypt(locationInputs[i], ciphertext, originalHash)
        plaintText.then(function (plaintTextResult) {
            return plaintTextResult
        })
    }

}

So here what I try to do is, this promise Like ojbect which is encryptionTool.decryp() should run in the for loop and then whichever of these promises resolves should be the return value of decryptMessage method. However, this encryptionTool.decrypt method uses webcrypto api so it doesnt have reject or catch methods because it is an promise like method.

Upvotes: 0

Views: 2066

Answers (1)

Matt McCutchen
Matt McCutchen

Reputation: 30919

Since a promise represents a value that will be available in the future, you can't test "now" (synchronously) which one succeeds. Instead, you have to use the available functions for manipulating and combining promises. I'm assuming encryptionTool.decrypt returns a PromiseLike<string>; if it's a PromiseLike<T> for some other T, just replace string with that T in the following.

First, you can use Promise.resolve to convert the PromiseLike<string> to a Promise<string>. Then, you want to use Promise.all to take an array of promises and give you back a promise of the array of results, so you can write a then callback to scan the array and take the result you want. A potential problem is that Promise.all rejects if any of the provided promises rejects, in which case you don't get to see the other results. So before you use Promise.all, you need to use catch to map the rejection to a sentinel value, such as null. The complete code would look something like this (I haven't tested it, so there may be mistakes):

    // Assuming that encryptionTool.decrypt returns a PromiseLike<string>,
    // promises will be a Promise<string | null>[].
    let promises = [];
    for (let i = 0; i <= locationInputs.length - 1; i++) {
        promises.push(
            Promise.resolve(encryptionTool.decrypt(locationInputs[i], ciphertext, originalHash))
            .catch((error) => null));
    }
    return Promise.all(promises).then((results) => {
        // results is a (string | null)[].
        // Look through it, find the non-null result and return it.
    });

Note that the final then call generates a promise for the final result, and you return this promise synchronously to the caller of decryptMessage.

An alternative approach would be to make decryptMessage an async function so you can program in a more familiar style. Replace public decryptMessage with public async decryptMessage and then use code like this:

    for (let i = 0; i <= locationInputs.length - 1; i++) {
        try {
            return await encryptionTool.decrypt(locationInputs[i], ciphertext, originalHash);
        } catch (e) {
            // Discard the exception and proceed to the next decryption attempt.
        }
    }
    // TODO: Decide what to return here if all decryptions fail.

Note that this way, each decryption does not start until after the previous one fails, so the process may take longer to complete depending on how the web crypto API is implemented.

Upvotes: 1

Related Questions