Ders Altı - Bildirimciler ve Abonelikler
İçindekiler
- Bildirimci paketi
- Değer türlerinin farklı taşıyıcıları
- Bir asenkron iterasyonu tüketmek için farklı araçlar
- For-await-of döngüsü
- ObserveIteration uyarlayıcısı
- ObserveIterator uyarlayıcısı
- Örneklerin Test Edilmesi
- Başarı örneği
- Promise örneği
- Yerel temsil örneği
- Genel temsil örneği
- AbonelikIterator örneği
- makeSubscriptionKit vs makePublishKit
- makeNotifierKit vs makeSubscriptionKit
- Kullanıcı Arayüzü (UI)
- Abone Verilerinin Saklanması
- Vat zincir depolamasına girin
Bildirimci Paketi
notifier paketi, "bir hizmetin durum değişikliklerini istemcilere bildirmesine izin verir. Özellikle, asenkron değer dizilerinin üretilmesi ve tüketilmesi için soyutlamalardır. Bildirimci veya abonelik, ilgilenen istemcilere güncellemeleri yayınlamak isteyen bir nesne tarafından kullanılabilir hale getirilir." - Bildirimciler ve Abonelikler
Değerlerin Farklı Taşıyıcı Türleri
Değerlerin kayıpsız taşıyıcısı, asenkron bir iterasyondaki tüm değerlerin doğru ve eksiksiz bir şekilde iletilmesini sağlayan bir mekanizmayı ifade eder. "Kayıpsız" terimi, iletim süreci sırasında hiçbir bilginin kaybolmadığını ve değerlerin tamamının aktarıldığını gösterir. Bu, tüm yayınlanan durumun, son olmayan değerlerin yanı sıra son değerin her zaman kullanılabilir olacağı anlamına gelir.
Değerlerin kayıplı taşıyıcısı, asenkron bir iterasyonda değerleri ileten bir mekanizmayı ifade eder, burada bazı değerler doğru şekilde iletilmeyebilir. "Kayıplı" terimi, iletim süreci sırasında bazı bilgilerin kaybolabileceğini veya atılabileceğini gösterir. Ancak, iterasyonun sonlanması veya son değeri yine de kayıpsız iletilir, böylece alıcı iterasyonun tamamlandığını bilir. Bu, daha yeni bir yayınlanan durum varsa, önceki yayınlanan durumu kaçırabileceğiniz anlamına gelir.
Önek-kayıplı taşıyıcı, yayıncının tamamen kayıpsız olabilmesi için yayıncının hızlı tüketimini gerektirir. "Önek-kayıplı" terimi, tüketici bir yineleyici isteği yapmadan önce yayıncı tarafından yayınlanan değerlerin kaybedilebileceği anlamına gelir, ancak bir yineleyici elde edildiğinde, o yineleyici tarafından numaralandırılan değerler tam ve doğru olacaktır. Bu, döndürülen yineleyiciden önce yayınlanan her şeyi kaçırabileceğiniz anlamına gelir. Ancak, döndürülen yineleyici, o yineleyicinin başlangıç noktasından itibaren yayınlanan her şeyi numaralandıracaktır.
SubscriptionKit, değerlerin kayıpsız taşıyıcısıdır; NotifierKit, değerlerin kayıplı taşıyıcısıdır; PublishKit, değerlerin önek-kayıplı taşıyıcısıdır;
Asenkron bir iterasyonu tüketmek için farklı araçlar
notifier paketi belgeleri, bir asenkron iterasyonu tüketmek için aşağıdaki iki farklı yaklaşımı kullanmayı önerir:
- JavaScript
for-await-of
sözdizimi observeIteration
uyarlayıcısıobserveIterator
uyarlayıcısı
Onların arasındaki farkı daha iyi anlamak için, kaynak kodlarını analiz edeceğiz.
Ancak önce, bir asenkron iterasyonu, yani yayıncıyı
, içereceğiz.
/**
* See the Paula example in the README
*
* @param {IterationObserver<Passable>} iterationObserver
* @returns {void}
*/
export const paula = (iterationObserver) => {
// Paula the publisher says
iterationObserver.updateState("a");
iterationObserver.updateState("b");
iterationObserver.finish("done");
};
iterationObserver
nesnesi, makeNotifierKit()
veya makeSubscriptionKit()
kullanılarak oluşturulur ve bunlar sırasıyla bir updater
veya publication
döndürür.
type IterationObserver<T> = {
updateState: (nonFinalValue: T) => void,
finish: (completion: T) => void,
fail: (reason: any) => void,
};
For-await-of döngüsü
İterasyonu for-await-of
döngüsü kullanarak tüketin:
- final olmayan değerleri ve iterasyonun tamamlanıp tamamlanmadığını veya başarısız olup olmadığını görebilir.
- başarısızlık sebebini görebilir,
- tamamlanma değerini göremez
/**
* README'deki Alice örneğine bakın
*
* @param {AsyncIterable<Passable>} asyncIterable
* @returns {Promise<Passable[]>}
*/
export const alice = async (asyncIterable) => {
const log = [];
try {
for await (const val of asyncIterable) {
log.push(["non-final", val]);
}
log.push(["finished"]);
} catch (reason) {
log.push(["failed", reason]);
}
return log;
};
Tamamlanma değerinin log dizisine eklenmediğini, yalnızca finished
durumunun eklendiğini göz önünde bulundurun.
Yukarıdaki işlevden dönen log'u yazdırırsak ve Paula'nın yayıncısını iterasyon olarak kabul edersek, sonuç şu şekilde olur:
// sonunda yazdırır
// non-final a
// non-final b
// the iteration finished
ObserveIteration adaptörü
İterasyonu observeIteration(asyncIterableP, iterationObserver)
adaptörü kullanarak tüketin:
- final olmayan değerleri ve iterasyonun tamamlanıp tamamlanmadığını veya başarısız olup olmadığını görebilir.
- başarısızlık sebebini görebilir,
- tamamlanma değerini görebilir
/**
* README'deki Bob örneğine bakın
*
* @param {ERef<AsyncIterable<Passable>>} asyncIterableP
* @returns {Promise<Passable[]>}
*/
export const bob = async (asyncIterableP) => {
const log = [];
const observer = harden({
updateState: (val) => log.push(["non-final", val]),
finish: (completion) => log.push(["finished", completion]),
fail: (reason) => log.push(["failed", reason]),
});
await observeIteration(asyncIterableP, observer);
return log;
};
Bu metodu kullanırken tamamlanma değerinin (completion
) log dizisine eklendiğini görebiliriz.
Yukarıdaki işlevden dönen log'u yazdırırsak ve Paula'nın yayıncısını iterasyon olarak kabul edersek, sonuç şu şekilde olur:
// sonunda yazdırır
// non-final a
// non-final b
// finished done
ObserveIterator adaptörü
Bir asenkron iterasyonu tüketmek için observeIteration
ve observeIterator
kullanmanın arasındaki fark, iterasyonla etkileşim kurmak için kullanılan soyutlama seviyesine atıfta bulunur. ObserveIterator'ın, carol()
, sadece subscriptions
için geçerli olduğunu hatırlamak önemlidir.
ObserveIteration, argüman olarak bir AsyncIterable
alır, bu daha yüksek düzeyde bir soyutlama olup iterasyonu tüketmek için daha uygun bir arayüz sağlar. Bu yaklaşım genellikle kullanması daha kolay ve daha rahattır ancak temel iterasyon süreci üzerinde daha fazla kontrol sağlamayabilir.
Diğer taraftan, observeIterator, argüman olarak bir AsyncIterator
alır, bu daha düşük düzeyde bir soyutlama olup temel yineleyiciye doğrudan erişim sağlar. Bu yaklaşım, iterasyon süreci üzerinde daha fazla kontrol sağlar, ancak iterasyonu yönetmek için daha karmaşık kod gerektirebilir ve kullanması daha az rahat olabilir.
Carol'ün observeIterator'ı doğrudan kullanma yaklaşımı genellikle özel asenkron veri yapılarını uygularken veya karmaşık veya son derece optimize edilmiş iterasyon süreçleriyle uğraşırken, iterasyon süreci üzerinde daha fazla kontrol gerektiren durumlarda kullanılır.
/**
* See the Carol example in the README. The Alice and Bob code above have
* been abstracted from the code in the README to apply to any IterationObserver
* and AsyncIterable. By contrast, the Carol code is inherently specific to
* subscriptions.
*
* @param {ERef<Subscription<Passable>>} subscriptionP
* @returns {Promise<Passable[]>}
*/
export const carol = async (subscriptionP) => {
const subscriptionIteratorP = E(subscriptionP)[Symbol.asyncIterator]();
const { promise: afterA, resolve: afterAResolve } = makePromiseKit();
const makeObserver = (log) =>
harden({
updateState: (val) => {
if (val === "a") {
// @ts-expect-error
afterAResolve(E(subscriptionIteratorP).subscribe());
}
log.push(["non-final", val]);
},
finish: (completion) => log.push(["finished", completion]),
fail: (reason) => log.push(["failed", reason]),
});
const log1 = [];
const observer1 = makeObserver(log1);
const log2 = [];
const observer2 = makeObserver(log2);
const p1 = observeIterator(subscriptionIteratorP, observer1);
// afterA is an ERef<Subscription> so we use observeIteration on it.
const p2 = observeIteration(afterA, observer2);
await Promise.all([p1, p2]);
return [log1, log2];
};
Paula'nın yayıncısını yinelediğimizi varsayarak, yukarıdaki işlevde döndürülen günlüğü yazdırırsak, sonuç şöyle olur:
[log1:
// eventually prints
// non-final a
// non-final b
// finished done
,
log2:
// eventually prints
// non-final b
// finished done
];
Test Örnekleri
Aşağıdaki örnekler, daha önce incelenen kodun etkinliğini ve işlevselliğini sergiler çünkü belirli yöntemleri içe aktarır ve kullanır.
import { paula, alice, bob, carol } from "./iterable-testing-tools.js";
Bu testler, makeSubscriptionKit()
kullanılarak yapılacaktır, ancak aynı mantık makeNotifierKit()
kullanıldığında da geçerlidir. Bu makenotifierkit-vs-makesubscriptionkit alt bölümde açıklanmıştır.
Başarılı örnek
İlk test, bir interation'ı tüketme iki yolun davranışını gösterir; alice()
içinde uygulanan for-await-of
sözdizimi ve bob()
içinde uygulanan observeIteration
adaptörü:
test("subscription for-await-of success example", async (t) => {
const { publication, subscription } = makeSubscriptionKit();
paula(publication);
const log = await alice(subscription);
t.deepEqual(log, [["non-final", "a"], ["non-final", "b"], ["finished"]]);
});
test("subscription observeIteration success example", async (t) => {
const { publication, subscription } = makeSubscriptionKit();
paula(publication);
const log = await bob(subscription);
t.deepEqual(log, [
["non-final", "a"],
["non-final", "b"],
["finished", "done"],
]);
});
Daha önce belirtildiği gibi, for-await-of yaklaşımı kullanıldığında, finished
durumunun değeri döndürülmez, observeIteration ise done
değerini döndürür.
Promise örneği
Bu testte, alice ve bob'a subscription
, yani bir AsyncIterable
nesnesi yerine bir promise
geçiriyoruz.
Dikkat edin ki, alice bir parametre olarak {AsyncIterable<Passable>} asyncIterable
alırken, bob {ERef<AsyncIterable<Passable>>} asyncIterableP
alır.
Bu, Promise üzerinden yinelemeye çalışırken alice'in kodunun hata vereceği, bob'un ise başarılı olacağı anlamına gelir.
test("subscription for-await-of cannot eat promise", async (t) => {
const { publication, subscription } = makeSubscriptionKit();
paula(publication);
const subP = Promise.resolve(subscription);
// Type cast because this test demonstrates the failure that results from
// giving Alice a promise for a subscription.
const log = await alice(/** @type {any} */ (subP));
// This TypeError is thrown by JavaScript when a for-await-in loop
// attempts to iterate a promise that is not an async iterable.
t.is(log[0][0], "failed");
t.assert(log[0][1] instanceof TypeError);
});
test("subscription observeIteration can eat promise", async (t) => {
const { publication, subscription } = makeSubscriptionKit();
paula(publication);
const subP = Promise.resolve(subscription);
const log = await bob(subP);
t.deepEqual(log, [
["non-final", "a"],
["non-final", "b"],
["finished", "done"],
]);
});
Yerel temsilci örneği
Bu test için, aşağıdaki kod parçasının amacını anlamamız gerekiyor:
const localSub = makeSubscription(E(subP).getSharableSubscriptionInternals());
Bu yöntem, bir Notifier/Subscription'ı ağı üzerinde etkili bir şekilde dağıtmak için kullanılır. Bunun için kopyalanacak Abonelikten bunu alır ve yeni siteye makeSubscription
'ı uygular, bu da o siteye eşdeğer bir yerel Notifier getirir.
test("subscription for-await-of on local representative", async (t) => {
const { publication, subscription } = makeSubscriptionKit();
paula(publication);
const subP = Promise.resolve(subscription);
const localSub = makeSubscription(E(subP).getSharableSubscriptionInternals());
const log = await alice(localSub);
t.deepEqual(log, [["non-final", "a"], ["non-final", "b"], ["finished"]]);
});
test("subscription observeIteration on local representative", async (t) => {
const { publication, subscription } = makeSubscriptionKit();
paula(publication);
const subP = Promise.resolve(subscription);
const localSub = makeSubscription(E(subP).getSharableSubscriptionInternals());
const log = await bob(localSub);
t.deepEqual(log, [
["non-final", "a"],
["non-final", "b"],
["finished", "done"],
]);
});
Alice ve Bob tarafından döndürülen sonuçlar, ilk testle aynı davranışı gösterecektir çünkü parametre olarak Promise subP
yerine aboneliği karşılayan bir AsyncIterable nesnesi alır. Bu, önceki testte alice() tarafından döndürülen hatayı çözer.
Bir bildirici promise nesnesini AsyncIterable'a çevirmenin başka bir yolu, makeAsyncIterableFromNotifier() metodunu kullanmaktır.
Genel temsil örneği
Bu test için, observeIteration()
'ın amacını anlamamız gerekiyor.
Aşağıdaki testlerden görebileceğimiz gibi, bu fonksiyon bir abonelik promise nesnesini ve yeni oluşturulan notifierKit()'in güncelleyicisi olacak bir iterationObserver parametre olarak alır.
observeIteration()
'ın amacı, abonelik Promise üzerinde çağrılan farklı metodları filtrelemektir.
/**
* This reads from `asyncIterableP` updating `iterationObserver` with each
* successive value. The `iterationObserver` may only be interested in certain
* occurrences (`updateState`, `finish`, `fail`), so for convenience,
* `observeIteration` feature tests for those methods before calling them.
*
* @template T
* @param {ERef<AsyncIterable<T>>} asyncIterableP
* @param {Partial<IterationObserver<T>>} iterationObserver
* @returns {Promise<undefined>}
*/
export const observeIteration = (asyncIterableP, iterationObserver) => {
const iteratorP = E(asyncIterableP)[Symbol.asyncIterator]();
return observeIterator(iteratorP, iterationObserver);
};
test("subscription for-await-of on generic representative", async (t) => {
const { publication, subscription } = makeSubscriptionKit();
paula(publication);
const subP = Promise.resolve(subscription);
const { publication: p, subscription: localSub } = makeSubscriptionKit();
await observeIteration(subP, p);
const log = await alice(localSub);
t.deepEqual(log, [["non-final", "a"], ["non-final", "b"], ["finished"]]);
});
test("subscription observeIteration on generic representative", async (t) => {
const { publication, subscription } = makeSubscriptionKit();
paula(publication);
const subP = Promise.resolve(subscription);
const { publication: p, subscription: localSub } = makeSubscriptionKit();
await observeIteration(subP, p);
const log = await bob(localSub);
t.deepEqual(log, [
["non-final", "a"],
["non-final", "b"],
["finished", "done"],
]);
});
Alice ve Bob tarafından döndürülen sonuçlar, aboneliği temsil eden AsyncIterable bir nesneyi argüman olarak aldığı için ilk testle aynı davranışı gösterecektir.
SubscriptionIterator örneği
Bu test, AsyncIterable abonelik nesnesini argüman olarak geçirerek ObserveIterator adaptör alt bölümünde tanımlanan observeIterator metodunu uygular.
// /////////////////////////////////////////////////////////////////////////////
// Carol is specific to subscription, so there is nothing analogous to the
// following in test-notifier-examples
test("subscribe to subscriptionIterator success example", async (t) => {
const { publication, subscription } = makeSubscriptionKit();
paula(publication);
const log = await carol(subscription);
t.deepEqual(log, [
[
["non-final", "a"],
["non-final", "b"],
["finished", "done"],
],
[
["non-final", "b"],
["finished", "done"],
],
]);
});
makeSubscriptionKit vs makePublishKit
agoric-sdk
yı keşfederken publishKit
adlı bir şeyi fark edebiliriz, ancak bunun herhangi bir akıllı sözleşmede kullanılmadığını görürüz. Bu küçük bir detay gibi görünebilir ancak yine de kafa karıştırabilir. Bu yüzden kısaca bundan bahsedeceğiz.
makeSubscriptionKit
'in uygulamasına bir göz atalım:
const makeSubscriptionKit = () => {
const { publisher, subscriber } = makePublishKit();
// The publish kit subscriber is prefix-lossy, so making *this* subscriber completely
// lossless requires eager consumption of the former.
// Such losslessness inhibits GC, which is why we're moving away from it.
const pubList = subscriber.subscribeAfter();
const subscription = makeSubscription(pubList);
/** @type {IterationObserver<T>} */
const publication = Far("publication", {
updateState: publisher.publish,
finish: publisher.finish,
fail: publisher.fail,
});
return harden({ publication, subscription });
};
Gördüğünüz gibi, metodun ilk satırında makePublishKit
publisher
ve base subscriber
oluşturur. base subscriber
terimini kullanıyoruz çünkü aşağıdaki satırlarda, publishKit'ten alınan bu abone, değerlerinin üzerinden yinelemeli olarak tüketilmesine ve publishKit'in prefix lossy
doğasını tamamlamasına izin veren bazı metotlarla sarılır.
const pubList = subscriber.subscribeAfter();
const subscription = makeSubscription(pubList);
makeNotifierKit vs makeSubscriptionKit
makeNotifierKit
testleri, dönen değerler dışında makeSubscriptionKit
ile aynı davranışı gösterecektir. Daha önce açıkladığımız gibi, makeNotifierKit'i kullanırken, onun kayıp değerlere karşı hassas doğası, yalnızca son güncellenen değeri döndürmesine neden olacaktır.
makeSubscriptionKit'in ilk testini örnek olarak kullanarak, işte makeNotifierKit'i kullanarak yapılan karşılık gelen test.
test("notifier for-await-of success example", async (t) => {
const { updater, notifier } = makeNotifierKit();
paula(updater);
const log = await alice(notifier);
t.deepEqual(last(log), ["finished"]);
});
test("notifier observeIteration success example", async (t) => {
const { updater, notifier } = makeNotifierKit();
paula(updater);
const log = await bob(notifier);
t.deepEqual(last(log), ["finished", "done"]);
});
Görebileceğimiz gibi, dönen log ['non-final', 'a'] ['non-final', 'b']
olmayan final değerlerini içermiyor.
Kullanıcı Arayüzü (UI)
Notifier paketi, blockchain'den verileri çıkararak kullanıcı arayüzünde
sunmak için son derece faydalıdır.
Aşağıdaki kodu inceleyelim, dapp-card-store UI'dan.
import { makeAsyncIterableFromNotifier as iterateNotifier } from '@agoric/notifier';
function App() {
...
...
publicFacetRef.current = publicFacet;
const availableItemsNotifier = E(
publicFacetRef.current,
).getAvailableItemsNotifier();
for await (const cardsAvailableAmount of iterateNotifier(
availableItemsNotifier,
)) {
setAvailableCards(cardsAvailableAmount.value);
}
};
...
}
export default App;
makeAsyncIterableFromNotifier()
metodu, Local representative example alt bölümünde açıklandığı gibi yerel bir bildirimci nesnesi oluşturur. Bu örnekte, geliştirici sözleşmenin kamuya açık yüzeyinden availableItemsNotifier
'ı alır, makeAsyncIterableFromNotifier kullanarak yerel bildirimci cardsAvailableAmount
'ı oluşturur ve bu bilgiyi grafiksel arayüzde göstermek için setAvailableCards'a argüman olarak geçer.
Not: agoric organization repo'sunda getSharableSubscriptionInternals() kullanılan UI örnekleri bulunamadı
Abone Verilerini Saklama
Agoric'ten veri okumak ücretsiz mi? Maalesef, şu anki yapıya göre, bu sorunun yanıtı hayır.
Agoric'te, blockchain'den güncellemeler almanın ana yolu Notifier
ve Subscriber
kullanmaktır.
Bir notifier için,
E(notifier).getUpdateSince();
Bir abone için,
subscriberIterator = subscriber[Symbol.asyncIterator]();
E(subscriberIterator).next();
Her iki işlem de blockchain'de yaşayan nesnelerin yöntemlerini çağırdığı için, bu işlemler gaz ücreti gerektirir. Agoric ekibi, ekosistemdeki büyük ölçekli uygulamaları barındırmak için bu soruna bir çözüm bulmaları gerektiğini fark etti.
İşte vat-chainStorage
Buna çözüm olarak, Agoric ekibi, anahtar-değer mağazası olarak hizmet verecek ayrı bir static vat
tasarladı.
Yazma erişimi, yalnızca inter-protocol
gibi uygun yeteneklere sahip diğer sistem düzeyi bileşenlerle sınırlıdır.
Öte yandan, okuma erişimi herkese açıktır. Bir storageNode
'un anahtarını bilen herkes verileri ücretsiz olarak okuyabilir.
Bu tür bir vat
ın tüm üçüncü taraf dapp geliştiricilere kullanılabilir hale getirilmesi için devam eden bir çaba var.
Bir
static-vat
ın, bootstrap sırasında başlatılan ve bir sistem bileşenini barındıran bir vat türü olduğunu hatırlayın.
Aşağıdaki diyagram, normal bir { publisher, subscriber }
çiftinin nasıl kullanılacağını göstermektedir.
vat-chainStorage
kullanımını yapmak için.
sequenceDiagram
actor Client
participant W as Wrapped Subscriber
participant S as Storage Node
participant P as Publisher
Client ->>+ W: getStoreKey() && getUnserializer()
W ->>- Client: storeKey && unserializer
P-->W: publisher.updateState()
W ->> S: storageNode.setValue()
Client ->>+ S: Read Data
S -->>- Client: { Data }
Aboneleri sarmak için uygulanan mekanizmalar vardır. Bu mekanizmaları storesub.js adresinde kontrol edebilirsiniz.