Web Locks API: وقتی چند تب باید با هم هماهنگ بشن
خلاصهٔ کاملتر
Web Locks API یک مسئلهٔ قدیمی اپهای وب رو حل میکنه: وقتی همون اپ تو چند تب یا چند worker همزمان بازه، چطور جلوی این رو بگیریم که همه با هم سراغ یک منبع مشترک برن. تو مستندات MDN اومده که این API به اسکریپت اجازه میده یک قفل رو بهشکل غیرهمگام بگیره، تا وقتی کار انجام میشه نگهش داره و بعد آزادش کنه؛ و تا وقتی قفل دست یکیه، هیچ اسکریپت دیگهای تو همون origin نمیتونه همون قفل رو بگیره.
قفل خودش یک مفهوم انتزاعیه که با یک اسم دلخواه — انتخاب خود اپ — نمایندهٔ یک منبع بالقوه مشترک میشه. مثال کلاسیک اینه که اپ تو چند تب بازه و میخوای فقط یک تب داده رو بین شبکه و IndexedDB سینک کنه: هر تب سعی میکنه قفلی مثل my_net_db_sync رو بگیره و فقط یکی موفق میشه. این همون الگوی انتخاب رهبر (leader election) ـه.
نقطهٔ ورود اصلی navigator.locks.request() ـه که یک اسم قفل، مجموعهای اختیاری از تنظیمات و یک callback میگیره. callback وقتی صدا زده میشه که قفل داده شده، و بهمحض این که برگرده قفل خودکار آزاد میشه — پس معمولاً یک تابع async ـه تا قفل تا پایان کامل کار دست خودت بمونه:
navigator.locks.request("my_resource", async (lock) => {
// قفل گرفته شد
await do_something();
await do_something_else();
// حالا قفل آزاد میشه
});خود request() هم یک promise برمیگردونه که بعد از آزاد شدن قفل resolve میشه؛ پس داخل یک تابع async میتونی await ـش کنی تا کد غیرهمگام خطی و خوانا بمونه. تا وقتی قفل دست یکیه، درخواستهای دیگه برای همون قفل — از همون اجرا یا از تبها و worker های دیگه — تو صف میشینن و اولین درخواست صف تازه بعد از آزاد شدن قفل اجازه میگیره.
تنظیمات هم چند گزینهٔ کاربردی دارن. mode بهطور پیشفرض exclusive ـه ولی میتونه shared باشه: یک قفل انحصاری فقط یک نگهدارنده داره، در حالی که چند درخواست اشتراکی همزمان اجازه میگیرن — همون الگوی خواننده‑نویسنده.
ifAvailable باعث میشه اگه قفل بیانتظار در دسترس نباشه درخواست شکست بخوره و callback با null صدا زده بشه. steal قفلهای نگهداشتهشده با همون اسم رو آزاد میکنه و درخواست تو رو جلوی صف میندازه. signal هم یک AbortSignal میگیره که با اون میشه درخواست رو لغو کرد یا برای گرفتن قفل timeout گذاشت.
برای عیبیابی، navigator.locks.query() وضعیت مدیر قفل همون origin رو برمیگردونه: یک اسنپشات از قفلهای نگهداشتهشده و درخواستشده بههمراه اطلاعاتی مثل mode شون — مفید برای وقتی که میخوای بفهمی چرا یک قفل گرفته نشد. برای حالتهای پیچیدهتر هم میتونی بهجای async function یک promise برگردونی که خودت هر وقت خواستی resolve یا reject ـش کنی، تا قفل بهمدت دلخواه دستت بمونه.
قفلها به origin محدودن؛ قفلهایی که تبی از میگیره هیچ اثری روی قفلهای تبی از یک origin دیگه ندارن. یک هشدار هم هست: اگه چند قفل بیترتیب درخواست بشن بست کامل (deadlock) ممکنه — تب ۱ قفل A رو داره و تب ۲ قفل B، و بعد هرکدوم قفل دیگری رو میخواد. راههای پرهیز: تودرتو نکردن درخواستها، رعایت ترتیب ثابت، یا گذاشتن timeout. خوشبختانه چنین بستی فقط خود قفلها و کد وابسته به اونها رو زمین میزنه، نه مرورگر و بقیهٔ تبها.
نکات کلیدی:
- گرفتن قفل نامدار غیرهمگام برای هماهنگی بین تبها و worker های یک origin
- کاربرد کلاسیک: انتخاب رهبر، مثل این که فقط یک تب با IndexedDB سینک کنه
- قفل با برگشتن callback خودکار آزاد میشه؛ request() هم promise آزادشدن رو میده
- گزینههای mode (shared/exclusive)، ifAvailable، steal و signal برای timeout
- از مارس ۲۰۲۲ روی مرورگرها Baseline ـه، ولی فقط تو بستر امن (HTTPS) کار میکنه




