// ───────────────────────────────────────────────────────────────────────────── // JS ERRORS, GENERATORS/ITERATORS, SYMBOLS, DATES, MODULES — the leftovers // ───────────────────────────────────────────────────────────────────────────── // ERRORS : throw new Error(msg, { cause }); custom classes via extends Error; // try / catch (e) / finally; catch binding optional; instanceof checks // Built-ins: Error TypeError RangeError SyntaxError ReferenceError AggregateError // ITER : [Symbol.iterator]() returns { next() -> { value, done } }; generators // (function*) build them for you; spread/for-of/destructure consume them // SAY : "fail fast", "error boundary", "rethrow with cause", "lazy evaluation", // "iterator protocol", "generator yields", "well-known symbols" // ───────────────────────────────────────────────────────────────────────────── 'use strict'; const assert = require('node:assert/strict'); // ── 1. Custom errors class ValidationError extends Error { constructor(field, message, options) { super(message, options); // options.cause supported Node 16.9+ this.name = 'ValidationError'; // otherwise name is 'Error' this.field = field; } } class NotFoundError extends Error { name = 'NotFoundError'; status = 404; } function parseAge(s) { const n = Number(s); if (!Number.isInteger(n) || n < 0) throw new ValidationError('age', `bad age: ${s}`); return n; } assert.equal(parseAge('42'), 42); assert.throws(() => parseAge('x'), ValidationError); assert.throws(() => parseAge('-1'), { name: 'ValidationError', field: 'age' }); // match by properties try { parseAge('x'); } catch (e) { assert.equal(e instanceof ValidationError && e instanceof Error, true); assert.equal(e.message, 'bad age: x'); assert.equal(typeof e.stack, 'string'); } try { try { JSON.parse('{'); } catch (e) { throw new Error('config load failed', { cause: e }); } } catch (e) { assert.equal(e.cause instanceof SyntaxError, true); // wrap and keep the original } assert.equal(new NotFoundError('nope').status, 404); // ── 2. try / catch / finally semantics function f() { try { return 'try'; } finally { sideEffect = 'finally ran'; } // finally runs even after return } let sideEffect = ''; assert.equal(f(), 'try'); assert.equal(sideEffect, 'finally ran'); function g() { try { throw new Error('x'); } catch { return 'caught'; } } // optional catch binding assert.equal(g(), 'caught'); function h() { try { return 1; } finally { return 2; } } // finally's return WINS (don't do this) assert.equal(h(), 2); assert.throws(() => { null.x; }, TypeError); assert.throws(() => { undefinedFn(); }, ReferenceError); assert.throws(() => new Array(-1), RangeError); assert.throws(() => JSON.parse('nope'), SyntaxError); assert.throws(() => { throw 'string'; }, /string/); // you CAN throw non-Errors; don't // Result-style alternative: const [err, val] = await to(promise) — or return { ok, value, error } // ── 3. Iterator protocol by hand const countdown = { from: 3, [Symbol.iterator]() { let n = this.from; return { next: () => (n > 0 ? { value: n--, done: false } : { value: undefined, done: true }) }; }, }; assert.deepEqual([...countdown], [3, 2, 1]); const [c1, c2] = countdown; assert.deepEqual([c1, c2], [3, 2]); assert.equal(Math.max(...countdown), 3); // ── 4. Generators function* idGen(start = 1) { let id = start; while (true) yield id++; } const ids = idGen(); assert.deepEqual([ids.next().value, ids.next().value], [1, 2]); function* range(lo, hi, step = 1) { for (let i = lo; i < hi; i += step) yield i; } assert.deepEqual([...range(0, 10, 3)], [0, 3, 6, 9]); function* take(n, it) { for (const x of it) { if (n-- <= 0) return; yield x; } } assert.deepEqual([...take(3, idGen(10))], [10, 11, 12]); // lazy: infinite source, finite consumption function* flatten(xs) { for (const x of xs) Array.isArray(x) ? yield* flatten(x) : yield x; } // yield* delegates assert.deepEqual([...flatten([1, [2, [3, [4]]]])], [1, 2, 3, 4]); function* twoWay() { const got = yield 'first'; yield `got ${got}`; } const tw = twoWay(); tw.next(); assert.equal(tw.next('hello').value, 'got hello'); // next(arg) sends a value INTO the generator function* inorder(node) { if (!node) return; yield* inorder(node.l); yield node.v; yield* inorder(node.r); } assert.deepEqual([...inorder({ v: 2, l: { v: 1 }, r: { v: 3 } })], [1, 2, 3]); assert.equal(typeof idGen().return, 'function'); // .return() / .throw() for cleanup & injection // ── 5. Symbols const id = Symbol('id'); const o = { [id]: 1, visible: 2 }; assert.deepEqual(Object.keys(o), ['visible']); // symbol keys are not enumerated assert.equal(o[id], 1); assert.equal(Symbol('a') === Symbol('a'), false); assert.equal(Symbol.for('a') === Symbol.for('a'), true); // global registry assert.equal(JSON.stringify(o), '{"visible":2}'); class Temp { constructor(c) { this.c = c; } [Symbol.toPrimitive](hint) { return hint === 'string' ? `${this.c}°C` : this.c; } } assert.equal(`${new Temp(21)}`, '21°C'); assert.equal(new Temp(21) + 1, 22); assert.equal(Object.prototype.toString.call({ [Symbol.toStringTag]: 'Thing' }), '[object Thing]'); // ── 6. Dates — enough for an ICA task that hands you timestamps const d = new Date(Date.UTC(2024, 0, 31, 12, 0, 0)); // months are 0-based! assert.equal(d.toISOString(), '2024-01-31T12:00:00.000Z'); assert.equal(d.getTime(), 1706702400000); // ms since epoch assert.equal(new Date('2024-01-31T12:00:00Z').getTime(), d.getTime()); assert.equal(new Date(0).toISOString(), '1970-01-01T00:00:00.000Z'); assert.equal(Math.floor((new Date('2024-03-01') - new Date('2024-02-01')) / 86_400_000), 29); // leap year assert.equal(d.getUTCDay(), 3); // Wednesday (0 = Sunday) const addDays = (date, n) => new Date(date.getTime() + n * 86_400_000); assert.equal(addDays(d, 1).toISOString().slice(0, 10), '2024-02-01'); assert.equal(Number.isNaN(new Date('garbage').getTime()), true); // "Invalid Date" assert.equal(typeof Date.now(), 'number'); // In ICA-style tasks timestamps are usually plain ints passed in. Never call Date.now() inside the logic. // ── 7. Modules — CommonJS vs ESM one-liners // CJS: const x = require('./x'); module.exports = { a }; exports.b = ...; __dirname available // ESM: import x from './x.js'; export const a = 1; export default ...; import.meta.url // Node picks ESM if "type": "module" in package.json or .mjs extension. CodeSignal uses a plain script — no imports needed. // ── 8. Numeric separators / optional catch / logical assignment / labeled break (rare but legal) assert.equal(1_000_000, 1000000); outer: for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { if (j === 1) continue outer; if (i === 2) break outer; } } assert.equal(typeof structuredClone, 'function'); // Node 17+ assert.equal(typeof globalThis.fetch, 'function'); // Node 18+ assert.equal(typeof queueMicrotask, 'function'); console.log('ok: javascript/errors-generators-misc.js');