microsoft / microsoft/TypeScript

Allow async functions to return union type T | Promise<T>

オープン
#33,595 コメント 17 件 リアクション 28 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

Awaiting More Feedback Suggestion
主要言語
Go
スター
111k
フォーク
14.3k
平均マージ
1日 19時間
マージ済み PR(30日)
117

説明

Search Terms

"the return type of an async function or method must be the global Promise type"
"typescript promise union type"

Suggestion

You can explicitly declare a function's return type as a union that includes a Promise<T>.
While this works when manually managing promises, it results in a compiler error when using the async/await syntax.

For example, a function can have a return type T | Promise<T>. As the developer of an abstraction layer, this allows you to have an abstract return type that can be handled in a typesafe way but doesn't dictate implementation to consumers.

This improves developer ergonomics for consumers of a library without reducing the safety of the type system by allowing developers to change implementation as the system evolves while still meeting the requirements of the abstraction layer.

This only works, currently, if the developer explicitly manages the promises. A developer may start with something like this:

type ActionResponse<T> = T | Promise<T>;

function getCurrentUsername(): ActionResponse<string> {
  return 'Constant Username';
}

async function logResponse<T>(response: ActionResponse<T>): Promise<void> {
  const responseValue = await response;
  console.log(responseValue);
}

logResponse(getCurrentUsername());
// Constant Username

Then, if the consumer of logResponse switches to a promise based method, there's no need to change the explicit return type:

function getCurrentUsername(): ActionResponse<string> {
  // return 'Constant Username';
  return Promise.resolve('Username from Database');
}

// Username from Database

However, if the consumer of logResponse prefers to use async/await instead of manually managing promises, this no longer works, yielding a compiler error instead:

The return type of an async function or method must be the global Promise type.

One workaround is to always return promises even when dealing non-async code:

async function getCurrentUsername(): Promise<string> {
  return 'Constant Username';
  // return Promise.resolve('Username from Database');
}

Another workaround is to use an implicit return type:

async function getCurrentUsername() {
  return Promise.resolve('Username from Database');
}

These do get around the issue for sure, but they impose restrictions on consumers of the abstraction layer causing it to leak into implementation.

It seems valuable for the behavior to be consistent between using async/await and using Promise directly.

Use Cases

This feature would be useful for developers who are building abstraction layers and would like to provide an abstract return type that could include promises. Some likely examples are middlewares, IoC containers, ORMs, etc.

In my particular case, it's with inversify-express-utils where the action invoked can be either async or not and the resulting behavior doesn't change.

Examples

// this type
type ActionResponse<T> = T | Promise<T>;

// supports this function
function getCurrentUsername(): ActionResponse<string> {
  return 'Constant Username';
}

// as it evolves over time into this function
async function getCurrentUsername(): ActionResponse<string> {
  return Promise.resolve('Username from Database');
}

// and is handled transparently by functions like this
async function logResponse<T>(response: ActionResponse<T>): Promise<void> {
  const responseValue = await response;
  console.log(responseValue);
}

logResponse(getCurrentUsername());

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

この issue には TypeScript コンパイラーのファイルもテストも記載されていません。まず、提供された例を使って報告された async の戻り値型エラーを再現し、次にコンパイラーの診断とそのカバレッジを特定します。async 関数が型安全性を維持したまま T | Promise を明示的に返せるようになれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
typescript
領域
compilers
issue の種類
機能追加
難易度
5/5
見積もり時間
1週間以上
活発さ
停滞
明瞭さ
おおむね明確
初心者へのやさしさ
35/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。