メインコンテンツまでスキップ

非同期ワークフロー

非同期APIの活用方法とベストプラクティスを説明します。

非同期APIとは

Report Flow APIの一部のエンドポイントは非同期処理をサポートしています:

API同期非同期
単一PDF生成/file/sync/single/file/async/single
複数PDF生成/file/sync/multiple/file/async/multiple

非同期処理のメリット

1. タイムアウト回避

同期APIは120秒でタイムアウトしますが、非同期APIは長時間の処理に対応できます。

// ❌ 同期API: 大量データで120秒を超える可能性
const pdf = await generateSyncPDF(largeData);

// ✅ 非同期API: タイムアウトなし
const { url } = await generateAsyncPDF(largeData);

2. バックグラウンド処理

ユーザーを待たせずに処理を実行できます。

// ユーザーリクエストを即座に受け付け
app.post('/api/generate-report', async (req, res) => {
// 非同期でPDF生成をキック
const { url, fileId } = await startPDFGeneration(req.body);

// 即座にレスポンス
res.json({
message: 'レポート生成を開始しました',
downloadUrl: url,
fileId
});

// 完了後にメール通知(バックグラウンド)
notifyWhenComplete(fileId, req.user.email);
});

3. 大量処理の並列化

複数のPDF生成を並列実行できます。

// 100件のPDFを並列生成
const requests = invoices.map(invoice =>
generateAsyncPDF({
designId: 'invoice-template',
content: invoice
})
);

const results = await Promise.all(requests);
console.log(`${results.length}件のPDFを生成しました`);

大量処理のパターン

パターン1: 並列実行(小〜中規模)

async function generateBulkPDFs(items, concurrency = 5) {
const chunks = chunkArray(items, concurrency);
const results = [];

for (const chunk of chunks) {
const promises = chunk.map(item =>
generateAsyncPDF({
designId: 'template-id',
content: item
})
);

const chunkResults = await Promise.all(promises);
results.push(...chunkResults);

console.log(`${results.length}/${items.length} 完了`);
}

return results;
}

function chunkArray(array, size) {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}

// 使用例: 100件を5件ずつ並列処理
const invoices = [...]; // 100件
const results = await generateBulkPDFs(invoices, 5);

パターン2: キューベース(大規模)

import Queue from 'bull';

// Redisベースのキューを作成
const pdfQueue = new Queue('pdf-generation', {
redis: {
host: 'localhost',
port: 6379
}
});

// ワーカー設定
pdfQueue.process(async (job) => {
const { designId, content } = job.data;

// PDF生成API呼び出し
const result = await generateAsyncPDF({ designId, content });

// 進捗更新
await job.progress(100);

return result;
});

// ジョブ追加
async function enqueuePDFGeneration(items) {
const jobs = items.map(item =>
pdfQueue.add({
designId: 'template-id',
content: item
}, {
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000
}
})
);

return Promise.all(jobs);
}

// イベント監視
pdfQueue.on('completed', (job, result) => {
console.log(`ジョブ ${job.id} 完了:`, result);
});

pdfQueue.on('failed', (job, err) => {
console.error(`ジョブ ${job.id} 失敗:`, err);
});

パターン3: バッチ処理(超大規模)

// 1000件以上の場合は複数PDFの一括生成を使用
async function generateMassiveBulk(items) {
const batchSize = 100; // APIの上限に合わせる
const batches = chunkArray(items, batchSize);

const results = [];

for (const [index, batch] of batches.entries()) {
console.log(`バッチ ${index + 1}/${batches.length} 処理中...`);

// 複数PDF一括生成API
const { url, files } = await generateMultiplePDFsAsync({
designId: 'template-id',
contents: batch.map(item => ({
fileName: `${item.id}.pdf`,
params: item
}))
});

results.push({ batchIndex: index, url, files });

// レート制限対策で少し待機
if (index < batches.length - 1) {
await sleep(1000);
}
}

return results;
}

エラーハンドリング

リトライロジック

async function generateWithRetry(params, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await generateAsyncPDF(params);
} catch (error) {
const isLastAttempt = attempt === maxRetries - 1;

if (isServerError(error) && !isLastAttempt) {
const delay = 1000 * Math.pow(2, attempt);
console.log(`リトライ ${attempt + 1}/${maxRetries} (${delay}ms後)`);
await sleep(delay);
continue;
}

throw error;
}
}
}

function isServerError(error) {
return error.response?.status >= 500;
}

部分的な失敗の処理

async function generateWithFallback(items) {
const results = [];
const errors = [];

for (const item of items) {
try {
const result = await generateAsyncPDF({
designId: 'template-id',
content: item
});
results.push({ item, result, status: 'success' });
} catch (error) {
console.error(`アイテム ${item.id} の生成失敗:`, error);
errors.push({ item, error, status: 'failed' });
}
}

return {
results,
errors,
summary: {
total: items.length,
success: results.length,
failed: errors.length
}
};
}

Python実装例

import asyncio
import aiohttp
from typing import List, Dict, Any

async def generate_pdf_async(session: aiohttp.ClientSession, params: Dict[str, Any]) -> Dict:
"""非同期でPDFを生成"""
url = "https://api.re-port-flow.com/v1/file/async/single"
headers = {
'appkey': params['app_key'],
'Content-Type': 'application/json'
}
data = {
'designId': params['design_id'],
'version': params['version'],
'content': params['content']
}

async with session.post(url, headers=headers, json=data) as response:
response.raise_for_status()
return await response.json()

async def generate_bulk_pdfs(items: List[Dict], workspace_id: str, concurrency: int = 5):
"""複数PDFを並列生成"""
semaphore = asyncio.Semaphore(concurrency)

async def generate_with_limit(item):
async with semaphore:
async with aiohttp.ClientSession() as session:
return await generate_pdf_async(session, {
'app_key': 'your-app-key',
'design_id': 'template-id',
'version': 1,
'content': item
})

tasks = [generate_with_limit(item) for item in items]
return await asyncio.gather(*tasks)

# 使用例
items = [...] # 生成対象データ
results = asyncio.run(generate_bulk_pdfs(items, '550e8400-...', concurrency=10))

ベストプラクティス

  1. 適切な並列度を設定: サーバー負荷とレスポンス速度のバランスを取る
  2. 指数バックオフを使用: ポーリング間隔を徐々に増やす
  3. タイムアウトを設定: 無限待機を防ぐ
  4. エラーをログ: 失敗したアイテムを追跡
  5. 進捗を表示: ユーザーに状況を伝える

次のステップ