تایپاسکریپت - وعدههای قابل بهروزرسانی با استفاده از پروکسی در جاوااسکریپت
ویرایش: من این سوال را بهروز کردهام و سوال قدیمی به اینجا منتقل شده است.
یک اثبات مفهوم را میتوان در این jsfiddle یا قطعه کد زیر یافت
هدف:
هدف اینجا این است که یک وعده ایجاد کنیم یا بهتر بگوییم شبیهسازی کنیم، که قابل بروزرسانی باشد و چرخه عمر آن به طور اساسی قابل بازنشانی باشد.
بهطور اختیاری، میخواستم کنترل بیشتری بر حل شدن promise با استفاده از معکوسسازی کنترل داشته باشم، مثل این:
const ex = {};
const prom = new Promise<T>((resolve, reject) => {
ex.resolve = resolve;
ex.reject = reject;
});
اجرا:
قطعه:
شما میتوانید مقدار جدید را تایپ کنید و انتخاب کنید که وعده را رد کنید یا حل کنید. مقادیر رد شده برجسته خواهند شد.
به طور پیشفرض وعده با یک مقدار
defaultرد میشود.
ویرایش: به طور پیشفرض، یک callback .then() وصل شده است (خط ۸۵ در قطعه کد). بنابراین وقتی برای اولین بار یک مقدار را حل یا رد کنید، مقدار شما دو بار نمایش داده میشود که این رفتار مورد انتظار است.
class UpdatablePromise {
/**
* @param prom - the default promise object
* @param executor - optional ExternalExecutor argument to store executor internally.
* @returns UpdatablePromise
*/
constructor(prom, executor) {
this.value = undefined;
this.executors = [];
if (executor) this.executors.push(Object.assign({}, executor));
/**
* Call previous resolve/reject functions to fulfil previous promises, if any.
* helper function, just to reduce code duplication in `update` function.
*/
const prevResolvers = (type) => {
return (val) => {
const flag = type === "reject";
while (true) {
const executor = this.executors.shift();
if (executor) flag ? executor.reject(val) : executor.resolve(val);
else break;
}
return val;
};
};
const handler = {
get: (target, prop) => {
// console.log(prop);
if (prop === "update") {
// return a function which takes the new promise value and its executor as argument
return (v, executor) => {
// console.log('upd', v, this.value);
this.value = v;
if (executor) this.executors.push(executor);
// attach `then` function to the new promise, so that the old promises are fulfilled when the new one does
// this has no effect on already fulfilled promises.
v.then(
(val) => prevResolvers("resolve")(val),
(val) => prevResolvers("reject")(val)
);
};
} else if (typeof this.value === "undefined") {
// if value is undefined, i.e. promise was never updated, return default property values
return typeof target[prop] === "function" ?
target[prop].bind(target) :
target[prop];
}
// else, attach functions to new promise
else if (prop === "then") {
// console.log('then', this.value);
return (onfulfilled, onrejected) =>
this.value.then(onfulfilled, onrejected);
} else if (prop === "catch") {
return (onrejected) => this.value.catch(onrejected);
} else if (prop === "finally") {
return (onfinally) => this.value.finally(onfinally);
} else return target[prop];
},
};
return new Proxy(prom, handler);
}
}
const input = document.getElementById("input")
const output = document.getElementById("output")
const resBtn = document.getElementById("res")
const rejBtn = document.getElementById("rej")
const ex_1 = {
resolve: () => void 0,
reject: () => void 0,
};
const prom_1 = new Promise((resolve, reject) => {
ex_1.resolve = resolve;
ex_1.reject = reject;
});
const up = new UpdatablePromise(prom_1, ex_1)
// Await the promise
up.then(_ => print(_), _ => print(_))
// Print the value of promise, highlight if it is rejected
async function print() {
try {
const val = await up;
output.innerHTML += `
${val}`
} catch (e) {
output.innerHTML += `
<mark>${e}</mark>`
}
}
resBtn.addEventListener("click", () => {
up.update(Promise.resolve(input.value));
print();
})
rejBtn.addEventListener("click", () => {
up.update(Promise.reject(input.value));
print();
})
function consoleTest() {
const ex = {
resolve: () => void 0,
reject: () => void 0,
};
const prom = new Promise((resolve, reject) => {
ex.resolve = resolve;
ex.reject = reject;
});
const up = new UpdatablePromise(prom, ex);
setTimeout(() => ex.resolve("resolved"), 1000);
up.then(
(_) => console.log(_, "res"),
(_) => console.log(_, "rej")
);
setTimeout(async() => {
up.update(Promise.reject("reject"));
up.then(
(_) => console.log(_, "res"),
(_) => console.log(_, "rej")
);
}, 2000);
setTimeout(async() => {
up.update(Promise.resolve("res again"));
up.then(
(_) => console.log(_, "res"),
(_) => console.log(_, "rej")
);
}, 2500);
}
consoleTest();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<label for="input">Enter a value to update promise with:</label>
<input id="input" />
<div>
<button id="res">
resolve
</button>
<button id="rej">
reject
</button>
</div>
<pre id="output"></pre>
</body>
</html>جیاس فیدل
زمین بازی ts - نسخه ts (ممکن است بهروز نباشد. نسخه fiddle ترجیح داده میشود)
در اینجا قسمتی است که مقدار وعده را چاپ میکند:
// Print the value of promise, highlight if it is rejected
async function print() {
try {
const val = await up;
output.innerHTML += `
${val}`
} catch (e) {
output.innerHTML += `
<mark>${e}</mark>`
}
}
مسئله:
به نظر میرسد که گاهی کار میکند، برای مثال، در کنسول یا در فیدل.
اما وقتی که از آن در سرویس ورکر استفاده میکنم واقعاً کار نمیکند، فقط مقدار قدیمی پرامیس بازگردانده میشود. من از ورک باکس استفاده میکنم، اگر مرتبط باشد.
من هنوز نتوانستهام مشکل را مشخص کنم. بنابراین این کد واقعی workbox است که برای من کار نمیکند:
// If the refresh Token is resolved, the user is logged in, otherwise not.
class MyStrat extends Strategy {
async _handle() {
try {
await refreshToken;
return new Response(JSON.stringify({ is_logged_in: true }), {
status: 200,
headers: [[CONTENT_TYPE, APPLICATION_JSON]],
});
} catch (e) {
console.log('error', e);
return new Response(JSON.stringify({ is_logged_in: false }), {
status: 400,
headers: [[CONTENT_TYPE, APPLICATION_JSON]],
});
}
}
}
سؤال دقیق:
میخواهم چند چیز را بدانم:
آیا واقعاً همانطور که در بالا ذکر شد کار می کند؟ (یعنی دقیقاً مانند یک وعده کار می کند، با این استثنا که می توان آن را "بازنشانی" کرد و پس از به روز رسانی، همه
انتظارهای معلق یا تماس های پیوست شده با مقدار به روز شده جدید انجام می شود)اگر بله، پس دلیل اینکه در سرویسورکر (workbox) کار نمیکند چیست؟
اگر نه، پس در واقع چه اتفاقی دارد میافتد؟
اگر واقعاً کار کند، آیا امکان دارد یک متغیر عضو
stateاضافه کنیم تا بهصورت همزمان وضعیت promise را پیگیری کند؟ (یک بار امتحانش کردم، اصلاً جواب نداد)
ویرایش ۲:
ظاهراً این واقعاً به نظر میرسد که در سرویس ورکر هم کار میکند. پرامیس به دلیل برخی توابع دیگر، به طور مکرر به مقدار ذخیره شده در پایگاه داده بهروزرسانی میشد.
با این حال، من هنوز هم میخواهم پاسخی برای سایر سوالهایم دریافت کنم
پاسخ شماره ۱
آیا واقعاً مثل انتظاری که از کار با کنسول یا فیدل دارم کار میکند؟
فقط شما میتوانید بگویید آیا این همان کاری را انجام میدهد که انتظار دارید یا نه. اما با توجه به اینکه گفتهاید در کنسول، در فیادل، و (طبق ویرایشتان) همچنین در سرویس ورکر هم کار میکند، به نظر میرسد که بله، واقعاً کار میکند.
با این حال، دلیلی خوب برای استفاده از Proxy در اینجا وجود ندارد. پیادهسازی بسیار تمیزتر میشود اگر از یک نمونه کلاس معمولی که یک پرامیس را در بر دارد استفاده کنید:
class UpdatableResource {
/**
* @param prom - the default promise object
* @param resolver - optional resolver function to resolve the promise when updating.
*/
constructor(prom, resolver) {
this.value = Promise.resolve(prom);
this.handlers = [];
if (resolver) this.handlers.push(resolver);
}
/**
* Call previous resolve/reject functions to fulfil previous promises, if any.
* helper function, just to reduce code duplication in `update` function.
*/
_runHandlers(val) {
while (this.handlers.length) {
const handler = this.handlers.shift();
handler(val);
}
}
/* a function which takes the new promise value and its executor as argument */
update(v, handler) {
// console.log('upd', v, this.value);
this.value = Promise.resolve(v);
// resolve the old promise(s) to the new one, so that the old promises are fulfilled when the new one does
this._runHandlers(v);
if (handler) this.handlers.push(handler);
}
then(onfulfilled, onrejected) {
return this.value.then(onfulfilled, onrejected);
}
catch(onrejected) {
return this.value.catch(onrejected);
}
finally(onfinally) {
return this.value.finally(onfinally);
}
}
آیا امکان دارد یک متغیر عضو
stateاضافه کنیم تا به صورت همزمان وضعیت پرامیس را پیگیری کند؟
مطمئناً، فقط اضافه کن
this.state = "pending";
this.value.then(() => {
this.state = "fulfilled";
}, () => {
this.state = "rejected";
});
بعد از هر اختصاص یک وعده به this.value (یا بهتر است، آن را در یک متد کمکی قرار دهید).
