404 Not Found

404 Not Found


nginx

TypeScriptにおける非同期プログラミングと型

非同期プログラミングはJavaScriptの中核となるパターンであり、TypeScriptでは、ジェネリックなPromiseやasync/awaitを通じて、非同期操作に対する完全な型サポートが追加されています。

1. Promise型の基礎

(1) プロミスの汎用型

Promise はジェネリック型であり、Promise<T> は「将来、型 T の値が生成される」ことを示しています:

TYPESCRIPT
// Synchronization Functions——Direct Return Value
function getUserSync(): string {
  return "Charlie";
}

// Asynchronous Functions——Back Promise<string>
function getUserAsync(): Promise<string> {
  return Promise.resolve("Charlie");
}

// Simulate Web Requests
function fetchUser(id: number): Promise<{ id: number; name: string }> {
  return new Promise((resolve) => {
    setTimeout(() => resolve({ id, name: "User" + id }), 100);
  });
}

(2) then および catch における型推論

TYPESCRIPT
let promise: Promise<string> = Promise.resolve("hello");

promise
  .then(value => {
    // value Type automatically inferred as string
    console.log(value.toUpperCase());  // ✅
    return value.length;               // Back number → then Chain becomes Promise<number>
  })
  .then(length => {
    // length Type automatically inferred as number
    console.log(length.toFixed(2));    // ✅
  })
  .catch(error => {
    // error Type: any(TypeScript Unable to infer the error type)
    console.log(error.message);
  });

(3) Promise型の不変性と安全性

TYPESCRIPT
// Promise It is covariant.——Promise<string> Can be assigned to Promise<string | number>
let p1: Promise<string> = Promise.resolve("hello");
let p2: Promise<string | number> = p1;  // ✅ Covariant Safety

// It doesn't work the other way around.
// let p3: Promise<string> = p2;  // ❌ Promise<string | number> Cannot be assigned Promise<string>

▶ 例:型安全な API クライアント

TYPESCRIPT
interface ApiResponse<T> {
  status: number;
  data: T;
  message: string;
}

interface User {
  id: number;
  name: string;
  email: string;
}

interface Product {
  id: number;
  title: string;
  price: number;
}

function apiGet<T>(url: string): Promise<ApiResponse<T>> {
  // Simulation API Call
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        status: 200,
        data: {} as T,  // In actual projects, this is done by JSON.parse Result Assertion
        message: "OK"
      });
    }, 100);
  });
}

// Usage——Each request has a specific return type
async function getUser(id: number): Promise<User> {
  let response = await apiGet<User>(`/api/users/${id}`);
  return response.data;
}

async function getProducts(): Promise<Product[]> {
  let response = await apiGet<Product[]>("/api/products");
  return response.data;
}

2. async/await における型

(1) 非同期関数の戻り値

非同期関数は常に Promise を返します。通常の値を返した場合でも、TypeScript が自動的にそれをラップします:

TYPESCRIPT
// Explicit Return Values——Automatic packaging is Promise
async function greet(): Promise<string> {
  return "Hello";  // Equivalent to return Promise.resolve("Hello")
}

// Explicit Return Promise——No double packaging
async function fetchName(): Promise<string> {
  return Promise.resolve("Charlie");  // It won't turn into Promise<Promise<string>>
}

(2) await による型推論

await は Promise を展開します — await Promise<T> の型は T です:

TYPESCRIPT
async function example() {
  let name: string = await Promise.resolve("Charlie");      // ✅ Unpack to string
  let count: number = await Promise.resolve(42);          // ✅ Unpack to number
  let user: User = await fetchUser(1);                     // ✅ Unpack to User
}

(3) 落とし穴に注意 — 却下される可能性があります

TYPESCRIPT
async function riskyOperation(): Promise<number> {
  // This Promise possibly reject
  let result = await mayFail();  // result The type is number——However, an exception may be thrown during runtime.
  return result;
}

async function mayFail(): Promise<number> {
  if (Math.random() > 0.5) {
    throw new Error("Random failure");
  }
  return 42;
}

// Safe coding practice — use try/catch
async function safeOperation(): Promise<number | null> {
  try {
    let result = await mayFail();
    return result;
  } catch {
    return null;
  }
}

3. Promise ユーティリティ型

(1) 待ち受け—Promise型の解明

TypeScript 4.5 には、Awaited<T> 型(Promise の再帰的なアンラップ)が導入されています:

TYPESCRIPT
type A = Awaited<Promise<string>>;               // string
type B = Awaited<Promise<Promise<number>>>;       // number(Recursive Unpacking)
type C = Awaited<string>;                         // string (non-Promise, returned directly)
type D = Awaited<Promise<string | number>>;       // string | number

(2) 実用例—非同期関数の戻り値の型を取得する

TYPESCRIPT
async function fetchUser(id: number) {
  let response = await fetch(`/api/users/${id}`);
  return response.json() as Promise<{ id: number; name: string }>;
}

// Extracting the Return Type of an Asynchronous Function(Remove Promise Packaging)
type UserResponse = Awaited<ReturnType<typeof fetchUser>>;
// { id: number; name: string }

(3) カスタム・プロミス・ユーティリティ型

TYPESCRIPT
// Extract Promise Each element in the array Promise Parsed Type
type UnwrapPromiseArray<T> = {
  [K in keyof T]: T[K] extends Promise<infer U> ? U : T[K];
};

type Input = [Promise<string>, Promise<number>, boolean];
type Output = UnwrapPromiseArray<Input>;
// [string, number, boolean]

4. 並行制御における型安全性

(1) Promise.all

TYPESCRIPT
async function loadDashboard() {
  // Promise.all Accepting different types of Promise Array
  let [users, products, stats] = await Promise.all([
    fetchUsers(),      // Promise<User[]>
    fetchProducts(),   // Promise<Product[]>
    fetchStats()       // Promise<Stats>
  ]);

  // Each variable has its own type.
  console.log(users.length);        // User[].length
  console.log(products[0].title);   // Product.title
  console.log(stats.totalUsers);    // Stats.totalUsers
}

(2) Promise.allSettled

TYPESCRIPT
type SettledResult<T> = {
  status: "fulfilled";
  value: T;
} | {
  status: "rejected";
  reason: unknown;
};

async function tryMultipleApis() {
  let results = await Promise.allSettled([
    fetchFromApi1(),  // Promise<User>
    fetchFromApi2(),  // Promise<User>
  ]);

  for (let result of results) {
    if (result.status === "fulfilled") {
      console.log(result.value.name);   // ✅ value Type: User
    } else {
      console.log(result.reason);       // unknown
    }
  }
}

(3) Promise.race と Promise.any

TYPESCRIPT
// race——Take the result that was completed first(Whether success or failure)
async function fetchWithTimeout(url: string, ms: number) {
  let result = await Promise.race([
    fetch(url),
    new Promise<never>((_, reject) =>
      setTimeout(() => reject(new Error("Timeout")), ms)
    )
  ]);
  return result;
}

// any——Take the first successful result(Ignore Failure)
async function fetchWithFallback() {
  let result = await Promise.any([
    fetchFromPrimary(),   // Priority
    fetchFromSecondary(), // Standby
  ]);
  // result Type: All Promise composite types' resolved values
}

▶ 例:並行データローダー

TYPESCRIPT
interface User { id: number; name: string; }
interface Post { id: number; title: string; authorId: number; }
interface Comment { id: number; postId: number; text: string; }

async function fetchAllData(userId: number) {
  let [user, posts, comments] = await Promise.all([
    fetchUser(userId),
    fetchPosts(userId),
    fetchComments(userId)
  ]);

  return { user, posts, comments };
}

async function fetchUser(id: number): Promise<User> {
  return { id, name: "User" + id };
}

async function fetchPosts(userId: number): Promise<Post[]> {
  return [{ id: 1, title: "Article1", authorId: userId }];
}

async function fetchComments(userId: number): Promise<Comment[]> {
  return [{ id: 1, postId: 1, text: "Great Article!" }];
}

async function main() {
  let data = await fetchAllData(1);
  console.log(`User:${data.user.name}`);
  console.log(`Number of Articles:${data.posts.length}`);
  console.log(`Number of comments:${data.comments.length}`);
}

main();

5. 非同期イテレータ

(1) for await...of を使用した AsyncIterable

TYPESCRIPT
async function* asyncCounter(max: number): AsyncGenerator<number> {
  for (let i = 0; i < max; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

async function run() {
  for await (let num of asyncCounter(3)) {
    console.log(num);
  }
  // 0 → 1 → 2(Every interval100ms)
}

(2) 非同期ジェネレータの種類

TYPESCRIPT
interface AsyncGenerator<T> {
  next(): Promise<IteratorResult<T>>;
  return(value?: any): Promise<IteratorResult<T>>;
  throw(e?: any): Promise<IteratorResult<T>>;
  [Symbol.asyncIterator](): AsyncGenerator<T>;
}

❓ よくある質問

Q async 関数は Promise を返さなければならないのでしょうか?
A 明示的に指定することをお勧めします。TypeScript は async 関数が Promise を返すことを推論できますが、明示的に指定することで async function fn(): Promise<T> より明確になります。これにより、ドキュメント作成が容易になり、コンパイル時にエラーを検出できるようになります。単純な関数では戻り値の型を省略することもできますが、複雑な関数では明示的に指定することを推奨します。
Q Promiseのcatchブロックでエラーの型が「unknown」となるのはなぜですか?
A JavaScriptでは(Errorに限定されず)任意の値がスローされるため、TypeScriptはキャッチされた値がError型であることを保証できません。catchブロック内で型チェックを行うこと if (error instanceof Error)、または型アサーションを使用すること error as Error が推奨されます。
Q Promise.all 内のプロミスのいずれかが失敗した場合はどうなりますか?
A Promise.all は「オール・オア・ナッシング」方式で動作します。つまり、1つでもプロミスが失敗すると、all 操作全体が失敗となります。「すべてが完了する(成功・失敗にかかわらず)」必要がある場合は、Promise.allSettledを使用してください。「最初に成功したもの」が必要な場合は、Promise.anyを使用してください。
Q 非同期関数はオーバーロードできますか?
A はい。非同期関数のオーバーロードは、同期関数とまったく同じように機能します。オーバーロードされたシグネチャは、さまざまなパラメータの組み合わせ(Promiseラッパー)に対する戻り値の型を記述しており、実装によってすべてのオーバーロード間でシグネチャの互換性が確保されます。

📖 まとめ

📝 練習問題

  1. 基本問題(難易度 ⭐):ms ミリ秒後に解決する Promise を返す delay(ms: number): Promise<void> という名前の関数を記述してください。async/await を使用してこの関数を呼び出し、「1 秒待ってからメッセージを出力する」という処理を実装してください。
  2. 上級問題(難易度 ⭐⭐)retry<T>(fn: () => Promise<T>, maxRetries: number): Promise<T> 関数を作成してください。fn が失敗した場合、最大 maxRetries 回まで自動的に再試行します。try/catch とループを使用してこれを実装してください。
  3. チャレンジ問題(難易度 ⭐⭐⭐)concurrentLimit<T>(tasks: (() => Promise<T>)[], limit: number): Promise<T[]> を実装してください。タスクを並行して実行しますが、同時に実行するタスクの数は上限を超えないようにしてください。すべてのタスクが完了したら、結果の配列(同じ順序で)を返してください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%