This skill should be used when the user asks to "combine multiple api calls", "parallel langdock action", "batch requests in langdock", "Promise.all langdock", "multi-endpoint action", or needs to...
Build efficient Langdock actions that combine multiple API calls with parallel execution and fault tolerance.
Use when all API calls are independent and can run simultaneously:
// name = Get Full Stock Data
// description = Fetches quote, profile, and news in parallel
// symbol = Stock ticker symbol (e.g. 'AAPL')
const symbol = data.input.symbol;
const apikey = data.auth.apikey;
const baseUrl = 'https://financialmodelingprep.com/api/v3';
const [quoteRes, profileRes, newsRes] = await Promise.all([
ld.request({
url: `${baseUrl}/quote/${symbol}?apikey=${apikey}`,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: null,
}),
ld.request({
url: `${baseUrl}/profile/${symbol}?apikey=${apikey}`,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: null,
}),
ld.request({
url: `${baseUrl}/stock_news?tickers=${symbol}&limit=5&apikey=${apikey}`,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: null,
}),
]);
return {
symbol: symbol,
quote: quoteRes.json[0],
profile: profileRes.json[0],
news: newsRes.json,
timestamp: new Date().toISOString(),
};
Use when partial success is acceptable and you need to handle failures gracefully:
// name = Get Market Overview
// description = Fetches multiple market indicators, continues on partial failure
// indices = Comma-separated index symbols (default: 'SPY,QQQ,DIA')
const indices = (data.input.indices || 'SPY,QQQ,DIA').split(',');
const apikey = data.auth.apikey;
const requests = indices.map(symbol =>
ld.request({
url: `https://api.example.com/quote/${symbol.trim()}?apikey=${apikey}`,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: null,
})
);
const results = await Promise.allSettled(requests);
const successful = [];
const failed = [];
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
successful.push({
symbol: indices[index],
data: result.value.json,
});
} else {
failed.push({
symbol: indices[index],
error: result.reason?.message || 'Unknown error',
});
}
});
return {
data: successful,
errors: failed,
successCount: successful.length,
failCount: failed.length,
timestamp: new Date().toISOString(),
};
Use when some calls depend on results from previous calls:
// name = Get Company Details with Financials
// description = First fetches company info, then fetches related financials in parallel
// symbol = Stock ticker symbol (e.g. 'AAPL')
const symbol = data.input.symbol;
const apikey = data.auth.apikey;
const baseUrl = 'https://financialmodelingprep.com/api/v3';
// Step 1: Get base company data (needed for some subsequent calls)
const profileRes = await ld.request({
url: `${baseUrl}/profile/${symbol}?apikey=${apikey}`,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: null,
});
const profile = profileRes.json[0];
// Step 2: Parallel calls that may use data from step 1
const [incomeRes, balanceRes, cashFlowRes] = await Promise.all([
ld.request({
url: `${baseUrl}/income-statement/${symbol}?limit=4&apikey=${apikey}`,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: null,
}),
ld.request({
url: `${baseUrl}/balance-sheet-statement/${symbol}?limit=4&apikey=${apikey}`,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: null,
}),
ld.request({
url: `${baseUrl}/cash-flow-statement/${symbol}?limit=4&apikey=${apikey}`,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: null,
}),
]);
return {
symbol: symbol,
company: {
name: profile.companyName,
industry: profile.industry,
sector: profile.sector,
marketCap: profile.mktCap,
},
financials: {
incomeStatement: incomeRes.json,
balanceSheet: balanceRes.json,
cashFlow: cashFlowRes.json,
},
timestamp: new Date().toISOString(),
};
Use when the first call determines what subsequent calls to make:
// name = Get Portfolio Summary
// description = Fetches details for all positions in a portfolio
// portfolioId = Portfolio ID to fetch
const portfolioRes = await ld.request({
url: `https://api.example.com/portfolios/${data.input.portfolioId}`,
method: 'GET',
headers: {
Authorization: `Bearer ${data.auth.apiKey}`,
'Content-Type': 'application/json',
},
body: null,
});
const positions = portfolioRes.json.positions;
// Build parallel requests based on portfolio contents
const detailRequests = positions.map(position =>
ld.request({
url: `https://api.example.com/quote/${position.symbol}`,
method: 'GET',
headers: {
Authorization: `Bearer ${data.auth.apiKey}`,
'Content-Type': 'application/json',
},
body: null,
})
);
const detailResults = await Promise.allSettled(detailRequests);
const enrichedPositions = positions.map((position, index) => {
const result = detailResults[index];
return {
...position,
currentPrice: result.status === 'fulfilled' ? result.value.json.price : null,
fetchError: result.status === 'rejected' ? result.reason?.message : null,
};
});
return {
portfolioId: data.input.portfolioId,
positions: enrichedPositions,
totalPositions: positions.length,
timestamp: new Date().toISOString(),
};
Always return a well-organized object:
return {
// Primary data from main request(s)
primaryData: response1.json,
secondaryData: response2.json,
// Metadata for context
metadata: {
requestedSymbol: data.input.symbol,
timestamp: new Date().toISOString(),
requestCount: 3,
successCount: 3,
},
// Error tracking (if using allSettled)
errors: failedRequests,
};
All requests must succeed or entire action fails:
try {
const [res1, res2, res3] = await Promise.all([
ld.request(options1),
ld.request(options2),
ld.request(options3),
]);
return { data1: res1.json, data2: res2.json, data3: res3.json };
} catch (error) {
return { error: true, message: error.message };
}
Continue with available data even if some requests fail:
const results = await Promise.allSettled([
ld.request(options1),
ld.request(options2),
ld.request(options3),
]);
const data = {};
const errors = [];
if (results[0].status === 'fulfilled') {
data.quotes = results[0].value.json;
} else {
errors.push({ source: 'quotes', error: results[0].reason?.message });
}
if (results[1].status === 'fulfilled') {
data.profile = results[1].value.json;
} else {
errors.push({ source: 'profile', error: results[1].reason?.message });
}
if (results[2].status === 'fulfilled') {
data.news = results[2].value.json;
} else {
errors.push({ source: 'news', error: results[2].reason?.message });
}
return { ...data, errors, hasErrors: errors.length > 0 };
Provide defaults when requests fail:
const results = await Promise.allSettled([
ld.request(quotesOptions),
ld.request(newsOptions),
]);
return {
quotes: results[0].status === 'fulfilled' ? results[0].value.json : [],
news: results[1].status === 'fulfilled' ? results[1].value.json : [],
dataComplete: results.every(r => r.status === 'fulfilled'),
};
When making many parallel requests, consider:
// Helper function to chunk array
function chunk(array, size) {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
// Process in batches of 5
const symbols = data.input.symbols.split(',');
const batches = chunk(symbols, 5);
const allResults = [];
for (const batch of batches) {
const batchResults = await Promise.all(
batch.map(symbol =>
ld.request({
url: `https://api.example.com/quote/${symbol}?apikey=${data.auth.apikey}`,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: null,
})
)
);
allResults.push(...batchResults.map(r => r.json));
}
return { quotes: allResults };
// name = Combined Financial Data
// description = Fetches income statement, balance sheet, and cash flow in parallel
// symbol = Stock ticker symbol (e.g. 'AAPL')
// period = Reporting period: 'annual' or 'quarterly' (default: 'annual')
// dataPoints = Comma-separated data to fetch: income,balance,cashflow (default: 'income,balance,cashflow')
// name = Comprehensive Stock Analysis
// description = Fetches quote, profile, financials, and news in optimized parallel batches
// symbol = Stock ticker symbol (e.g. 'AAPL')
// includeNews = Include recent news (default: true)
// financialPeriods = Number of financial periods (default: 4)
const symbol = data.input.symbol;
const includeNews = data.input.includeNews !== false;
const periods = data.input.financialPeriods || 4;
const apikey = data.auth.apikey;
const baseUrl = 'https://financialmodelingprep.com/api/v3';
// Build request list based on options
const requests = [
ld.request({
url: `${baseUrl}/quote/${symbol}?apikey=${apikey}`,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: null,
}),
ld.request({
url: `${baseUrl}/profile/${symbol}?apikey=${apikey}`,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: null,
}),
ld.request({
url: `${baseUrl}/income-statement/${symbol}?limit=${periods}&apikey=${apikey}`,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: null,
}),
ld.request({
url: `${baseUrl}/key-metrics/${symbol}?limit=${periods}&apikey=${apikey}`,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: null,
}),
];
if (includeNews) {
requests.push(
ld.request({
url: `${baseUrl}/stock_news?tickers=${symbol}&limit=5&apikey=${apikey}`,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: null,
})
);
}
const results = await Promise.allSettled(requests);
const getValue = (index) =>
results[index].status === 'fulfilled' ? results[index].value.json : null;
const getError = (index) =>
results[index].status === 'rejected' ? results[index].reason?.message : null;
const errors = [];
if (getError(0)) errors.push({ source: 'quote', error: getError(0) });
if (getError(1)) errors.push({ source: 'profile', error: getError(1) });
if (getError(2)) errors.push({ source: 'financials', error: getError(2) });
if (getError(3)) errors.push({ source: 'metrics', error: getError(3) });
if (includeNews && getError(4)) errors.push({ source: 'news', error: getError(4) });
return {
symbol: symbol,
quote: getValue(0)?.[0] || null,
profile: getValue(1)?.[0] || null,
financials: getValue(2) || [],
metrics: getValue(3) || [],
news: includeNews ? (getValue(4) || []) : null,
metadata: {
timestamp: new Date().toISOString(),
requestCount: requests.length,
successCount: results.filter(r => r.status === 'fulfilled').length,
includesNews: includeNews,
periodCount: periods,
},
errors: errors.length > 0 ? errors : null,
};
Promise.all for strict requirements, Promise.allSettled for fault tolerance