ข้ามไปเนื้อหาหลัก

Category: reference

JavaScript Modern APIs (2023–2025)

APIs ใหม่ที่ใช้ได้ใน modern browsers และ Node.js: Object.groupBy, Array methods ใหม่, structuredClone, Promise.withResolvers, และอื่นๆ

· อ่านประมาณ 5 นาที

สารบัญ

Array Methods ใหม่ (ES2023+)

toSorted / toReversed / toSpliced — ไม่ mutate

const nums = [3, 1, 4, 1, 5];

// ❌ เดิม: mutate array ต้นฉบับ
nums.sort();     // nums เปลี่ยนแล้ว!
nums.reverse();  // nums เปลี่ยนแล้ว!

// ✓ ใหม่: คืน array ใหม่ ต้นฉบับไม่เปลี่ยน
const sorted = nums.toSorted();         // [1, 1, 3, 4, 5]
const reversed = nums.toReversed();     // [5, 1, 4, 1, 3]
const spliced = nums.toSpliced(2, 1);   // [3, 1, 1, 5] (ลบ index 2)
const withNew = nums.with(0, 99);       // [99, 1, 4, 1, 5] (แทน index 0)

nums;  // [3, 1, 4, 1, 5] ← ไม่เปลี่ยน

// มีประโยชน์มากกับ React state:
setItems(items.toSorted((a, b) => a.name.localeCompare(b.name)));

Array.at() — Negative Indexing

const arr = [1, 2, 3, 4, 5];

arr.at(0)   // 1
arr.at(-1)  // 5 (element สุดท้าย)
arr.at(-2)  // 4

// ✓ เทียบกับ arr[arr.length - 1] ที่ verbose กว่า
const last = arr.at(-1);

// ใช้กับ string ด้วย
'hello'.at(-1)  // 'o'

Object.groupBy / Map.groupBy (ES2024)

const people = [
  { name: 'Alice', dept: 'Engineering' },
  { name: 'Bob',   dept: 'Design' },
  { name: 'Carol', dept: 'Engineering' },
  { name: 'Dave',  dept: 'Design' },
];

// ✓ groupBy — คืน object ที่ key คือ group
const byDept = Object.groupBy(people, (p) => p.dept);
// {
//   Engineering: [{ name: 'Alice', ... }, { name: 'Carol', ... }],
//   Design: [{ name: 'Bob', ... }, { name: 'Dave', ... }]
// }

// Map.groupBy — เมื่อ key ไม่ใช่ string (object, symbol, etc.)
const byNameLength = Map.groupBy(people, (p) => p.name.length);
// Map { 5 => [...], 3 => [...], 4 => [...] }

// เทียบกับ reduce เดิม:
const byDeptOld = people.reduce((acc, p) => {
  (acc[p.dept] ??= []).push(p);
  return acc;
}, {});

structuredClone — Deep Clone ที่ถูกต้อง

// ❌ JSON round-trip: ไม่รองรับ Date, undefined, circular refs
const clone = JSON.parse(JSON.stringify(obj));

// ❌ spread/assign: shallow copy เท่านั้น
const clone = { ...obj };  // nested objects ยัง reference เดียวกัน

// ✓ structuredClone: deep clone ที่ถูกต้อง
const original = {
  name: 'Alice',
  created: new Date('2024-01-01'),
  tags: ['ts', 'css'],
  nested: { score: 100 },
};

const clone = structuredClone(original);
clone.tags.push('node');
clone.nested.score = 200;

original.tags;         // ['ts', 'css'] — ไม่เปลี่ยน
original.nested.score; // 100 — ไม่เปลี่ยน
clone.created;         // Date object (ไม่ใช่ string)

// รองรับ: Date, RegExp, Map, Set, ArrayBuffer, TypedArray, Error
// ไม่รองรับ: Function, Symbol, DOM nodes, class instances (เสีย methods)

Promise.withResolvers (ES2024)

// ❌ เดิม: verbose boilerplate
let resolve, reject;
const promise = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});
// ใช้ resolve/reject ภายนอก...

// ✓ ใหม่: clean API
const { promise, resolve, reject } = Promise.withResolvers();

// ตัวอย่าง: wrap event เป็น Promise
function waitForEvent(element, eventName) {
  const { promise, resolve, reject } = Promise.withResolvers();
  const controller = new AbortController();

  element.addEventListener(eventName, resolve, { once: true, signal: controller.signal });
  setTimeout(() => {
    controller.abort();
    reject(new Error(`Timeout waiting for ${eventName}`));
  }, 5000);

  return promise;
}

await waitForEvent(button, 'click');

Nullish Coalescing Assignment (??=, &&=, ||=)

// ??= : assign ถ้า null หรือ undefined เท่านั้น
let user = null;
user ??= { name: 'Guest' };   // user = { name: 'Guest' }

let count = 0;
count ??= 10;   // count ยังเป็น 0 (ไม่ใช่ null/undefined)

// ||= : assign ถ้า falsy (0, '', false, null, undefined)
let title = '';
title ||= 'Untitled';  // title = 'Untitled'

// &&= : assign ถ้า truthy
let config = { debug: false };
config &&= { ...config, version: '1.0' };  // config = { debug: false, version: '1.0' }

// ใช้บ่อยมากสำหรับ cache:
const cache = {};
function getUser(id) {
  cache[id] ??= fetchUser(id);  // โหลดครั้งเดียว
  return cache[id];
}

Error.cause (ES2022)

// ✓ ส่ง original error เป็น cause
try {
  await db.connect(url);
} catch (err) {
  throw new Error('Database connection failed', { cause: err });
}

// อ่าน cause:
try {
  await connectDB();
} catch (err) {
  console.error(err.message);  // 'Database connection failed'
  console.error(err.cause);    // original db error
}

Array.fromAsync (ES2024)

// สร้าง array จาก async iterable
async function* generateItems() {
  for (let i = 0; i < 5; i++) {
    await delay(100);
    yield { id: i, value: i * 2 };
  }
}

const items = await Array.fromAsync(generateItems());
// [{ id: 0, value: 0 }, { id: 1, value: 2 }, ...]

// เทียบกับ
const items = [];
for await (const item of generateItems()) {
  items.push(item);
}

using / await using — Explicit Resource Management (ES2024)

// using: cleanup อัตโนมัติเมื่อออก scope (sync)
{
  using file = openFile('data.txt');
  // ใช้ file...
}  // ← file.close() ถูกเรียกอัตโนมัติ

// await using: async cleanup
async function processData() {
  await using db = await Database.connect(url);
  await using tx = await db.beginTransaction();
  // ใช้ tx...
}  // ← tx.rollback() หรือ tx.commit() และ db.disconnect() ถูกเรียกอัตโนมัติ

// implement Disposable interface:
class FileHandle {
  [Symbol.dispose]() {
    this.close();  // ถูกเรียกเมื่อ using block จบ
  }
}

Browser Support Summary

FeatureChromeFirefoxSafariNode.js
Array.at()929015.416.6
toSorted/Reversed1101151620
Object.groupBy11712117.421
structuredClone989415.417
Promise.withResolvers11912117.422
??= / &&= / ||=85791415
using keyword13422