Typescript Promise rejection type

JavascriptTypescriptPromise

Javascript Problem Overview


How do I set the type of the rejection of my promise? Let's say I do:

const start = (): Promise<string> => {
   return new Promise((resolve, reject) => {
      if (someCondition) {
         resolve('correct!');
      } else {
         reject(-1);
      }
   });
}

Let's say I want to reject with a number. But I cannot set the type; I can pass whatever I want to the reject here.

Moreover, when using this promise, I want to have compiling error if I use the rejection response type incorrectly.

Javascript Solutions


Solution 1 - Javascript

As explained in this issue, Promise doesn't have different types for fulfilled and rejected promises. reject accepts any argument that doesn't affect type of a promise.

Currently Promise cannot be typed any better. This results from the fact that a promise can be rejected by throwing inside then or catch (this is a preferable way to reject existing promise), and this cannot be handled by typing system; also, TypeScript also doesn't have exception-specific types except never.

Solution 2 - Javascript

Cause there is no way to set error type in some cases like Promise, or exception throws, we can work with errors in rust-like style:

// Result<T, E> is the type used for returning and propagating errors.
// It is an sum type with the variants,
// Ok<T>, representing success and containing a value, and 
// Err<E>, representing error and containing an error value.
export type Ok<T> = { _tag: "Ok"; ok: T };
export type Err<E> = { _tag: "Err"; err: E };
export type Result<T, E> = Ok<T> | Err<E>;
export const Result = Object.freeze({
  Ok: <T, E>(ok: T): Result<T, E> => ({ _tag: "Ok", ok }),
  Err: <T, E>(err: E): Result<T, E> => ({ _tag: "Err", err }),
});

const start = (): Promise<Result<string, number>> => {
  return new Promise((resolve) => {
    resolve(someCondition ? Result.Ok("correct!") : Result.Err(-1));
  });
};

start().then((r) => {
  switch (r._tag) {
    case "Ok": {
      console.log(`Ok { ${r.ok} }`);
      break;
    }
    case "Err": {
      console.log(`Err { ${r.err} }`);
      break;
    }
  }
});

Solution 3 - Javascript

> The exception is typed any because we cannot guarantee the correct > type of the exception at design time, and neither TypeScript nor > JavaScript provide the ability to guard exception types at run time. Your best option is to use type guards to provide both a design-time and run-time check in your code.

source

Solution 4 - Javascript

What @EstusFlask mentioned in his answer is correct.

> But I want go one step near to an artificial solution to > simulate what we want with TypeScript capabilities.

 

Sometimes I use this pattern in my codes:

interface IMyEx{
   errorId:number;
}

class MyEx implements IMyEx{
   errorId:number;
   constructor(errorId:number) {
      this.errorId = errorId;
   }
}
// -------------------------------------------------------
var prom = new Promise(function(resolve, reject) {
     try {
         if(..........)
            resolve('Huuuraaa');         
         else
            reject(new MyEx(100));
	 }
     catch (error) {
            reject(new MyEx(101));
	 }
});

// -------------------------------------------------------
prom()
.then(success => {
	try {
		}
    catch (error) {
        throw new MyEx(102);
	}
})
.catch(reason=>{
    const myEx = reason as IMyEx;
	if (myEx && myEx.errorId) {
       console.log('known error', myEx)
    }else{
       console.log('unknown error', reason)
    }
})
	

Solution 5 - Javascript

You can use a proxy to explicitly force resolve and reject argument types. The following example does not try to mimic the constructor of a promise - because I didn't find that useful in practice. I actually wanted to be able to call .resolve(...) and .reject(...) as functions outside of the constructor. On the receiving side the naked promise is used - e.g., await p.promise.then(...).catch(...).

export type Promolve<ResT=void,RejT=Error> = {
  promise: Promise<ResT>;
  resolve: (value:ResT|PromiseLike<ResT>) => void;
  reject:(value:RejT) =>void
};

export function makePromolve<ResT=void,RejT=Error>(): Promolve<ResT,RejT> {
  let resolve: (value:ResT| PromiseLike<ResT>)=>void = (value:ResT| PromiseLike<ResT>)=>{}
  let reject: (value:RejT)=>void = (value:RejT)=>{}
  const promise = new Promise<ResT>((res,rej) => {
    resolve = res;
    reject = rej;
  });
  return { promise, resolve, reject };
}

The let statements look as if they are pointless - and they are pointless at runtime. But it stops compiler errors that were not easy to resolve otherwise.

(async()=>{
  const p = makePromolve<number>();
  //p.resolve("0") // compiler error
  p.resolve(0);
  // p.reject(1) // compiler error 
  p.reject(new Error('oops')); 

  // no attempt made to type the receiving end 
  // just use the named promise
  const r = await p.promise.catch(e=>e); 
})()

As shown, calls to .resolve and .reject are properly typed checked.

No attempt is made in the above to force type checking on the receiving side. I did poke around with that idea, adding on .then and .catch members, but then what should they return? If they return a Promise then it goes back to being a normal promise, so it is pointless. And it seems there is no choice but to do that. So the naked promise is used for await, .then and .catch.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKoushaView Question on Stackoverflow
Solution 1 - JavascriptEstus FlaskView Answer on Stackoverflow
Solution 2 - JavascriptKEIIIView Answer on Stackoverflow
Solution 3 - JavascriptMikhail VasinView Answer on Stackoverflow
Solution 4 - JavascriptRamin BateniView Answer on Stackoverflow
Solution 5 - JavascriptCraig HicksView Answer on Stackoverflow