Throw in none async func

Is there a way to use throw errors (throw Error.reject) in none async function?
Or is there a way to throw an exception in a query function in a none async context?


In one of my canister I have got the following query function:

public shared query({ caller }) func getStorage() : async ?BucketId {
        let result: {#bucketId: ?BucketId; #error: Text;} = storagesStore.getBucket(caller);

        switch (result) {
            case (#error error) {
                throw Error.reject(error);
            };
            case (#bucketId bucketId) {
                switch (bucketId) {
                    case (?bucketId) {
                        return ?bucketId;
                    };
                    case null {
                        return null;
                    };
                };
            };
        };
    };

I’ve made my storage generic and therefore would like to refactor it to use it multiple times:

public shared query({ caller }) func getStorage(): async (?BucketId) {
        return getBucket<StorageBucket>(caller, storagesStore);
    };

public shared query({ caller }) func getWhateverBucket(): async (?BucketId) {
        return getBucket<AnotherTypeOfBucket>(caller, myOtherStore);
    };

private func getBucket<T>(caller: Principal, store: BucketsStore.BucketsStore<T>): (?BucketId) {
        let result: {#bucketId: ?BucketId; #error: Text;} = store.getBucket(caller);

        switch (result) {
            case (#error error) {
                throw Error.reject(error);
            };
            case (#bucketId bucketId) {
                switch (bucketId) {
                    case (?bucketId) {
                        return ?bucketId;
                    };
                    case null {
                        return null;
                    };
                };
            };
        };
    };

However, I cannot do so as throw Error.reject needs an async context (misplaced throw).

1 Like

No, unfortunately you cannot abstract over throw, just like you can’t directly abstract over await. The “official” solution is to have the helper function also have an async return type, then this works. But

  • You can then only return sharable type (although it’s not actually serialized, but transferred in the heap)
  • You have to call that function in await, which means that the commit/rollback behavior is changed
  • Because of this is a self-inter-canister-call, this is not possible from query methods

For more background on this design, Support direct abstraction of code that awaits into functions, without requiring an unnecessary async · Issue #1482 · dfinity/motoko · GitHub and Locally abstracting over async calls · Issue #680 · dfinity/motoko · GitHub are relevant.

1 Like

Thanks for the detailed answer.

These functions (from my code snippet) are called from my webapp and have to be performant, therefore I cannot transform the helper to be async.

Don’t know what the best practice but, I guess then if I want to spare code I’ll have to drop throw Error and probably transform the return type to a variant that contains either the textual Error or the effective Result.

1 Like

Yeah, I had to use a Result for something like this.

Result is a new addition to the language?

I tried following in motoko playground but get the error unbound type Result :man_shrugging:

import Result "mo:base/Result";

actor Echo {

  public query func say(phrase : Text) : async (Result<Text, Text>) {
    return  #ok "yolo" ;
  };

};

This import Result ... imports the module Result, but not the type defined in this module. So you’ll need to write Result.Result<Text, Text>.

3 Likes

Thanks! Yes, it goes better after an early morning coffee :wink: