1894 字
9 分鐘

Iterator.join 與 Iterator.zip 怎麼用?Chrome 153 新 API 與 fallback

處理 generator、串流或大型資料集時,常見的做法是先用 Array.from() 把 iterator 展開,再呼叫 join() 或用索引把多組資料配在一起。這樣雖然直觀,卻會失去 iterator 的延遲特性,也容易忘記 iterator 只能被消費一次。

Chrome 153 在 2026 年 9 月 8 日的 stable release 中加入 Iterator.prototype.join()Iterator.zip()Iterator.zipKeyed()。直接答案是:有限資料要轉成文字時用 iterator 的 join();要把多組 iterable 按位置配對時用 Iterator.zip(),已有欄位名稱則優先用 Iterator.zipKeyed() 但這批 API 目前仍不是所有瀏覽器的穩定基線,正式環境一定要先做 feature detection 和 fallback。

先確認支援邊界#

Chrome 官方 release notes 把這三個 API 列為 Chrome 153 的 JavaScript 變更;MDN 目前仍將 Iterator.prototype.join() 標為 experimental。這兩個訊號要一起看:它已經可以在指定的 Chrome 版本測試,不代表 Firefox、Safari、舊版 Chromium 或目前所有 Node.js runtime 都能直接執行。

我在本機的 Node.js v24.13.0 檢查結果是 Iterator 存在,但 Iterator.prototype.joinIterator.zipIterator.zipKeyed 尚未提供。因此不要把「本機 Node 能執行其他 iterator helper」當成這三個方法已經可用:

console.table({
iterator: typeof globalThis.Iterator,
join: typeof globalThis.Iterator?.prototype?.join,
zip: typeof globalThis.Iterator?.zip,
zipKeyed: typeof globalThis.Iterator?.zipKeyed,
});

用能力偵測取代 User-Agent 判斷:

const supportsIteratorJoin =
typeof globalThis.Iterator?.prototype?.join === 'function';
const supportsIteratorZip =
typeof globalThis.Iterator?.zip === 'function';
const supportsIteratorZipKeyed =
typeof globalThis.Iterator?.zipKeyed === 'function';

Iterator.prototype.join():不用先建立陣列#

join() 是 iterator instance method,行為接近 Array.prototype.join():依序消費 iterator,把值轉成字串,再用指定分隔符串起來。沒有傳分隔符時,預設使用逗號;nullundefined 會變成空字串。

function* changedFiles() {
yield 'src/app.js';
yield 'src/styles.css';
yield 'tests/app.test.js';
}
const summary = changedFiles().join('、');
console.log(summary);
// src/app.js、src/styles.css、tests/app.test.js

這個寫法少了一個中間陣列,但不是「完全不讀取資料」:join() 必須走完整個有限 iterator 才能得到最後字串,所以它不是 lazy operation。無限 generator 不能直接呼叫 join(),否則程式會一直等待。

Iterator 只能消費一次#

iterator 通常是有狀態的物件。第一次 join() 會把值全部取走,第二次再用同一個 iterator 得到的只會是空字串:

const values = [1, 2, 3][Symbol.iterator]();
console.log(values.join('-')); // 1-2-3
console.log(values.join('-')); // 空字串

如果同一份資料需要產生兩種輸出,保留可重複建立 iterator 的 iterable,或在需求確實需要時保存陣列;不要把已消費的 iterator 傳給另一個處理階段後,才期待它能重新開始。

舊瀏覽器的 join() fallback#

最簡單的 fallback 是 Array.from(iterable).join()

function joinIterable(iterable, separator = ',') {
const iterator = iterable[Symbol.iterator]();
if (typeof iterator.join === 'function') {
return iterator.join(separator);
}
// fallback 會把所有值 materialize,適合有限且可接受記憶體成本的資料。
return Array.from(iterator).join(separator);
}
console.log(joinIterable(new Set(['a', 'b', 'c']), ' / '));
// a / b / c

如果資料量很大,應改成逐步寫入輸出 buffer、限制最大筆數,或使用支援 streaming 的資料格式。fallback 的目標是保留功能,不是假裝它和原生 iterator operation 有相同的記憶體特性。

Iterator.zip():按位置配對多組資料#

Iterator.zip() 接收一個「由多組 iterable 組成的 iterable」,每次輸出一個陣列:第一組的第一項配第二組的第一項,依此類推。它適合把 API 回傳的 id、名稱和狀態按同一個索引組成列資料:

const ids = [101, 102, 103];
const names = ['首頁', '搜尋', '文章'];
const statuses = ['ready', 'ready', 'draft'];
const rows = Iterator.zip([ids, names, statuses]);
for (const [id, name, status] of rows) {
console.log({ id, name, status });
}

輸出的 iterator 會在每次 next() 時向各個來源各取一項,不必先把所有欄位轉成同一個大陣列。可是來源 iterable 仍然有副作用時,必須把取值順序和停止時機當成 API 合約測試;這不是把任意非同步資料流自動變成 async iterator。

shortestlongeststrict 怎麼選#

不同來源長度不一致時,第二個參數的 mode 決定結果:

mode行為適合情況
shortest(預設)任一來源結束就停止,其他 iterator 會被關閉以最短欄位為有效資料範圍
longest等所有來源結束,較短來源用 padding 補值報表要保留最長欄位的每一列
strict來源沒有同時結束時丟出 TypeError欄位長度不一致代表資料錯誤

例如匯入資料時,通常不應該默默截掉多出來的 id,strict 會更安全:

try {
const rows = Iterator.zip(
[[101, 102, 103], ['首頁', '搜尋']],
{ mode: 'strict' },
);
console.log([...rows]);
} catch (error) {
if (error instanceof TypeError) {
console.error('欄位長度不一致,停止匯入');
}
}

若是選 longest,可以明確填入每一組來源的補值:

const rows = Iterator.zip(
[[101, 102, 103], ['首頁', '搜尋']],
{ mode: 'longest', padding: [null, '未命名'] },
);
console.log([...rows]);
// [ [101, '首頁'], [102, '搜尋'], [103, '未命名'] ]

padding 只在 longest 模式有意義;不要設定了 padding 就以為 shortest 會自動補齊。

Iterator.zipKeyed():用欄位名稱避免順序搞錯#

如果來源本來就是以欄位名稱分組,zipKeyed() 會輸出物件,而不是必須靠陣列位置記憶欄位:

const columns = {
id: [101, 102, 103],
name: ['首頁', '搜尋', '文章'],
status: ['ready', 'ready', 'draft'],
};
for (const { id, name, status } of Iterator.zipKeyed(columns, {
mode: 'strict',
})) {
console.log(`${id}: ${name} (${status})`);
}

zip() 適合已有陣列欄位或需要用位置處理的資料;zipKeyed() 適合表格、API response 或多個命名欄位。後者的優點不是輸出比較快,而是欄位名稱會留在結果裡,降低把 idstatus 順序對調的風險。

Progressive enhancement 的實作方式#

不要在模組載入時無條件呼叫新 API。可以把支援判斷留在功能邊界,並為不支援的環境提供小而明確的 fallback:

function* zipShortest(iterables) {
const iterators = iterables.map((value) => value[Symbol.iterator]());
while (true) {
const results = iterators.map((iterator) => iterator.next());
if (results.some((result) => result.done)) return;
yield results.map((result) => result.value);
}
}
function* zipKeyedShortest(columns) {
const keys = Object.keys(columns);
for (const values of zipShortest(keys.map((key) => columns[key]))) {
yield Object.fromEntries(
values.map((value, index) => [keys[index], value]),
);
}
}
function rowsFromColumns(columns) {
if (typeof globalThis.Iterator?.zipKeyed === 'function') {
return globalThis.Iterator.zipKeyed(columns, { mode: 'strict' });
}
// 這個 fallback 只實作 shortest,沒有偽造 strict 的錯誤語意。
return zipKeyedShortest(columns);
}

這個 fallback 只適合你明確接受的簡化需求。若應用程式依賴 strictlongest 的補值、iterator 關閉或完整的例外語意,應選擇經審查的 polyfill/函式庫,並把測試放在支援矩陣中,而不是自行複製半套規格。

上線前至少測這四種情況:

  • 新 API 存在與不存在時,主要流程都能完成。
  • join() 的輸入是空 iterator、含 nullundefined,以及很大的有限資料。
  • zip() 的欄位長度相同、不相同,並分別驗證三種 mode
  • iterator 被讀過一次後,不會被錯誤地重複傳給第二個 consumer。

常見問題#

Q: Iterator.join()Array.from(iterator).join() 一定更快嗎?#

A: 不能直接保證。原生 join() 少了中間陣列,但仍然要消費完整 iterator;實際成本取決於資料來源、字串大小和 runtime。它的主要價值是讓 iterator pipeline 不必為了串接文字而先改成陣列。

Q: Chrome 153 stable 支援,就可以不做 fallback 嗎?#

A: 不行。Chrome release notes 只代表指定 Chrome channel 的支援;MDN 仍標示部分 API 為 experimental,而且其他瀏覽器與 Node.js 版本可能尚未提供。以 feature detection、你的瀏覽器支援矩陣和實際測試決定是否啟用。

Q: zip()zipKeyed() 該選哪個?#

A: 已經有陣列欄位、結果也適合用陣列解構時選 zip();來源是命名欄位時選 zipKeyed()。如果長度不一致是資料錯誤,兩者都建議使用 mode: 'strict',不要讓預設的 shortest 靜默截斷資料。

參考資料:

Chrome for Developers:Chrome 153 release notes

MDN:Iterator.prototype.join()

MDN:Iterator.zip()

MDN:Iterator.zipKeyed()

TC39:Joint Iteration

Iterator.join 與 Iterator.zip 怎麼用?Chrome 153 新 API 與 fallback
https://laplusda.com/posts/javascript-iterator-join-zip-chrome-153/
作者
Zero
發佈於
2026-09-14
許可協議
CC BY-NC-SA 4.0
這篇文章有幫助嗎?

回報錯字、失效連結,或告訴我你想看的延伸主題。