All skills
ccheney avatar

/modern-javascript

@00695ba

Implement or debug JavaScript language behavior, including promise completion, async iteration, mutation, and runtime compatibility. Use for ECMAScript semantics or polyfill/transform choices; not unrelated framework or TypeScript type design.

Use this Skill: https://skilld.dev/gh/ccheney/robust-skills/modern-javascript

This session only. Nothing lands on disk.

referencesPROMISES.md

≈1.8k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Promises and Async/Await

Promise creation, Promise.withResolvers(), async/await, try/catch, error-first returns, top-level await, Promise.all(), Promise.allSettled(), Promise.race(), Promise.any(), anti-patterns to avoid.

Contents

Promise Fundamentals

Creating Promises

// Basic Promise
const basic = new Promise((resolve, reject) => {
  setTimeout(() => {
    if (success) {
      resolve(result);
    } else {
      reject(new Error('Failed'));
    }
  }, 1000);
});

// ES2024: Promise.withResolvers()
const { promise, resolve, reject } = Promise.withResolvers();
// Control from outside
someEvent.on('complete', resolve);
someEvent.on('error', reject);

// Already resolved/rejected
const resolved = Promise.resolve(42);
const rejected = Promise.reject(new Error('Failed'));

// ES2025: Promise.try() — start a chain from a function that may
// throw synchronously; sync throws become rejections
const attempt = Promise.try(() => riskyOperation());

Promise Chaining

Prefer async/await (see below) for most cases. Use .then() for simple transforms or when you need the callback style.

fetchUser(userId)
  .then(user => fetchPosts(user.id))
  .then(posts => processPosts(posts))
  .then(result => console.log(result))
  .catch(error => console.error(error))
  .finally(() => cleanup());

Async/Await

Basic Usage

async function getUserData(userId) {
  const user = await fetchUser(userId);
  const posts = await fetchPosts(user.id);
  const comments = await fetchComments(posts[0].id);
  return { user, posts, comments };
}

Error Handling Patterns

Let a rejection propagate unless this layer can recover, translate it, add useful context, or decide the final outcome. Catching, logging, and rethrowing at every layer commonly duplicates logs. Choose an owning boundary deliberately.

// Try/catch
async function withTryCatch() {
  try {
    const result = await riskyOperation();
    return result;
  } catch (error) {
    return defaultValue;
  }
}

// Error-first return (Go-style)
async function withErrorReturn() {
  try {
    const result = await riskyOperation();
    return [null, result];
  } catch (error) {
    return [error, null];
  }
}

const [error, data] = await withErrorReturn();
if (error) handleError(error);
else processData(data);

// Wrapper utility
function to(promise) {
  return promise
    .then(data => [null, data])
    .catch(error => [error, null]);
}

const [err, user] = await to(fetchUser(id));

Top-Level Await (ES2022)

// In ES modules (not CommonJS)
const config = await loadConfig();
const db = await connectDatabase(config);

export { db };

Promise Combinators

Promise.all()

Wait for all inputs; reject as soon as one rejects. It does not cancel the other operations.

// Parallel execution
const [users, posts, comments] = await Promise.all([
  fetchUsers(),
  fetchPosts(),
  fetchComments()
]);

// With error handling
try {
  const results = await Promise.all([taskA(), taskB(), taskC()]);
} catch (error) {
  // Rejects when one input rejects; other operations keep running
  console.error('One task failed:', error);
}

Promise.allSettled()

Wait for all; get status of each.

const results = await Promise.allSettled([
  fetchFromPrimary(),
  fetchFromBackup(),
  fetchFromCache()
]);

const successes = results
  .filter(r => r.status === 'fulfilled')
  .map(r => r.value);

const failures = results
  .filter(r => r.status === 'rejected')
  .map(r => r.reason);

Promise.race()

First to settle (resolve or reject) wins. It does not cancel the losers. Use it only when losing operations may continue, or arrange cancellation through the operation's own protocol.

// fetch, AbortController, and timers are host APIs, not ECMAScript.
async function fetchWithTimeout(url, ms) {
  const controller = new AbortController();
  const timerId = setTimeout(
    () => controller.abort(new Error('Timeout')),
    ms,
  );

  try {
    return await fetch(url, { signal: controller.signal });
  } finally {
    clearTimeout(timerId);
  }
}

// First responder
const data = await Promise.race([
  fetchFromServer1(),
  fetchFromServer2()
]);

Promise.any()

First to succeed wins; fails only if all fail.

// Fallback pattern
try {
  const data = await Promise.any([
    fetchFromPrimary(),
    fetchFromSecondary(),
    fetchFromTertiary()
  ]);
} catch (error) {
  // AggregateError with all failures
  console.error('All sources failed:', error.errors);
}

Anti-Patterns

Unnecessary async

// ❌ Unnecessary wrapper
async function getUser(id) {
  return await fetchUser(id);
}
// ✅ Just return the promise
function getUser(id) {
  return fetchUser(id);
}

Sequential when parallel is possible

// ❌ Sequential (slow) — each await waits for the previous
const users = await fetchUsers();
const posts = await fetchPosts();
const comments = await fetchComments();
// ✅ Parallel (fast)
const [users, posts, comments] = await Promise.all([
  fetchUsers(),
  fetchPosts(),
  fetchComments()
]);

Choose an owning error boundary

Ordinary rejection propagation is a valid async contract. Do not catch merely because an error can escape to the caller.

// Lower-level function deliberately propagates rejection.
async function buildReport() {
  const data = await fetchData();
  return transform(data);
}
// A job boundary owns logging and the final failure outcome.
async function runReportJob() {
  try {
    await buildReport();
    return { ok: true };
  } catch (error) {
    logger.error('Report job failed:', error);
    return { ok: false, error };
  }
}

Mixing callbacks and promises

// ❌ Callback hell in async function
async function mixed() {
  fs.readFile('file.txt', (err, data) => {
    // This doesn't work with await
  });
}
// ✅ Promisify callbacks
import { promisify } from 'node:util';
const readFile = promisify(fs.readFile);

async function clean() {
  const data = await readFile('file.txt');
  return data;
}
// ✅ Or better, use the promise-based API directly
import { readFile } from 'node:fs/promises';

const data = await readFile('file.txt', 'utf8');

Creating promise inside loop

// ❌ Promises created but not awaited properly
async function bad() {
  items.forEach(async item => {
    await processItem(item);  // This doesn't wait!
  });
  // Function returns before items are processed
}

// ✅ Use for...of for sequential
async function sequential() {
  for (const item of items) {
    await processItem(item);
  }
}

// ✅ Use Promise.all for parallel
async function parallel() {
  await Promise.all(items.map(item => processItem(item)));
}

Source: SKILL.md on GitHub

No alerts14d4 checks · Risk SAFE
  • Gen Agent Trust Hub14d

    The skill provides a technical reference for modern JavaScript features and toolchains. It includes an indirect prompt injection surface by directing the agent to read and analyze project files, such as lockfiles and build configurations, which may contain untrusted data that could influence the agent's code generation logic.

  • Socket14d

    No alerts

  • Snyk14d

    Risk: LOW · No issues

  • Runlayer7mo

    12 files scanned · No issues

Signed by skilld at 00695ba. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 weeks ago.

Activeupdated 3 weeks ago

README badge

README badge for ccheney/robust-skills/modern-javascript