WebAssembly'den eşzamansız web API'lerini kullanma

Ingvar Stepanyan
Ingvar Stepanyan

Web'deki G/Ç API'leri asenkrondur ancak çoğu sistem dilinde senkrondur. Kodu WebAssembly'e derleyirken bir API türünü diğerine bağlamanız gerekir. Bu köprü Asyncify'dir. Bu yayında, Asyncify'yi ne zaman ve nasıl kullanacağınızı ve işleyiş şeklini öğreneceksiniz.

Sistem dillerinde G/Ç

C dilinde basit bir örnekle başlayacağım. Bir dosyadan kullanıcının adını okumak ve kullanıcıyı "Merhaba (kullanıcı adı)" mesajıyla karşılamak istediğinizi varsayalım:

#include <stdio.h>

int main() {
    FILE *stream = fopen("name.txt", "r");
    char name[20+1];
    size_t len = fread(&name, 1, 20, stream);
    name[len] = '\0';
    fclose(stream);
    printf("Hello, %s!\n", name);
    return 0;
}

Örnek çok fazla işlem yapmasa da her boyuttaki bir uygulamada bulabileceğiniz bir şeyi göstermektedir: Dış dünyadan bazı girişleri okur, bunları dahili olarak işler ve çıkışları dış dünyaya geri yazar. Dış dünyayla yapılan tüm bu etkileşimler, genellikle G/Ç olarak kısaltılan ve giriş/çıkış işlevleri olarak adlandırılan birkaç işlev aracılığıyla gerçekleşir.

C'den adı okumak için en az iki önemli G/Ç çağrısına ihtiyacınız vardır: Dosyayı açmak için fopen ve dosyasındaki verileri okumak için fread. Verileri aldıktan sonra sonucu konsola yazdırmak için başka bir G/Ç işlevi printf kullanabilirsiniz.

Bu işlevler ilk bakışta oldukça basit görünür ve veri okumak veya yazmak için gereken mekanizmalar hakkında iki kez düşünmeniz gerekmez. Ancak ortama bağlı olarak, cihazın içinde oldukça fazla işlem gerçekleşebilir:

  • Giriş dosyası yerel bir sürücüdeyse uygulamanın dosyayı bulmak, izinleri kontrol etmek, okumak için açmak ve ardından istenen bayt sayısı alıncaya kadar blok halinde okumak için bir dizi bellek ve disk erişimi gerçekleştirmesi gerekir. Bu işlem, diskinizin hızına ve istenen boyuta bağlı olarak oldukça yavaş olabilir.
  • Alternatif olarak, giriş dosyası bağlı bir ağ konumunda olabilir. Bu durumda, ağ yığını da dahil edilir ve her işlem için karmaşıklık, gecikme ve olası yeniden deneme sayısı artar.
  • Son olarak, printf komutunun bile konsola yazdıracağı garanti edilmez ve bir dosyaya veya ağ konumuna yönlendirilebilir. Bu durumda, yukarıdaki adımlardan aynı şekilde uygulanması gerekir.

Özetlemek gerekirse G/Ç yavaş olabilir ve belirli bir çağrının ne kadar süreceğini koda hızlıca göz atarak tahmin edemezsiniz. Bu işlem çalışırken uygulamanızın tamamı donmuş ve kullanıcıya yanıt vermiyormuş gibi görünür.

Bu, C veya C++ ile de sınırlı değildir. Çoğu sistem dili, tüm G/Ç'yi senkronize API'ler biçiminde sunar. Örneğin, örneği Rust'a çevirirseniz API daha basit görünebilir ancak aynı ilkeler geçerlidir. Tek yapmanız gereken bir çağrı yapmak ve sonucun döndürülmesini eşzamanlı olarak beklemektir. Bu sırada, tüm pahalı işlemler gerçekleştirilir ve sonuç tek bir çağrıda döndürülür:

fn main() {
    let s = std::fs::read_to_string("name.txt");
    println!("Hello, {}!", s);
}

Ancak bu örneklerden herhangi birini WebAssembly'e derlemeye ve web'e çevirmeye çalıştığınızda ne olur? Belirli bir örnek vermek gerekirse "dosya okuma" işlemi ne anlama gelebilir? Bazı depolama alanlarından veri okuması gerekir.

Web'in zaman uyumsuz modeli

Web'de eşleyebileceğiniz çeşitli depolama alanı seçenekleri vardır. Örneğin, bellek içi depolama alanı (JS nesneleri), localStorage, IndexedDB, sunucu tarafı depolama alanı ve yeni Dosya Sistemi Erişimi API'si.

Ancak bu API'lerden yalnızca ikisi (bellek içi depolama ve localStorage) eşzamanlı olarak kullanılabilir. Her ikisi de neleri ne kadar süreyle depolayabileceğiniz konusunda en kısıtlayıcı seçeneklerdir. Diğer tüm seçenekler yalnızca asenkron API'ler sağlar.

Bu, web'de kod yürütmenin temel özelliklerinden biridir: G/Ç işlemlerini içeren, zaman alan tüm işlemlerin eşzamansız olması gerekir.

Bunun nedeni, web'in geçmişte tek iş parçacıklı olması ve kullanıcı arayüzüne dokunan tüm kullanıcı kodlarının kullanıcı arayüzüyle aynı iş parçacığında çalışması gerekmesidir. CPU zamanı için düzen, oluşturma ve etkinlik işleme gibi diğer önemli görevlerle rekabet etmesi gerekir. Bir JavaScript veya WebAssembly parçasının "dosya okuma" işlemi başlatıp işlemin tamamlanması için milisaniyeler ila birkaç saniye arasında sekmenin tamamını veya geçmişte tarayıcının tamamını engellemesini istemezsiniz.

Bunun yerine, kodun yalnızca bir G/Ç işlemini, tamamlandıktan sonra yürütülecek bir geri çağırma işleviyle birlikte planlamasına izin verilir. Bu tür geri çağırmalar, tarayıcının etkinlik döngüsü kapsamında yürütülür. Burada ayrıntılara girmeyeceğim ancak etkinlik döngüsünün temel işleyiş şeklini öğrenmek istiyorsanız bu konuyu ayrıntılı olarak açıklayan Görevler, mikro görevler, sıralamalar ve planlamalar başlıklı makaleyi inceleyin.

Kısaca cevap vermek gerekirse tarayıcı, tüm kod parçalarını sıraya göre tek tek alarak sonsuz bir döngüde çalıştırır. Bir etkinlik tetiklendiğinde tarayıcı, ilgili işleyiciyi sıraya ekler ve bir sonraki döngü iterasyonunda işleyici sıradan alınıp yürütülür. Bu mekanizma, yalnızca tek bir iş parçacığı kullanırken eşzamanlılığı simüle etmenize ve çok sayıda paralel işlem yürütmenize olanak tanır.

Bu mekanizmayla ilgili olarak hatırlamanız gereken önemli nokta, özel JavaScript (veya WebAssembly) kodunuz yürütülürken etkinlik döngüsünün engellenmesidir. Engellenmiş durumdayken harici işleyicilere, etkinliklere, G/Ç'ye vb. tepki veremezsiniz. G/Ç sonuçlarını geri almanın tek yolu, bir geri çağırma işlevi kaydetmek, kodunuzu yürütmeyi tamamlamak ve bekleyen görevleri işlemeye devam edebilmesi için kontrolü tarayıcıya geri vermektir. G/Ç tamamlandığında işleyiciniz bu görevlerden biri haline gelir ve yürütülür.

Örneğin, yukarıdaki örnekleri modern JavaScript'de yeniden yazmak ve uzak bir URL'den bir ad okumaya karar vermek isterseniz Fetch API'yi ve async-await söz dizimini kullanırsınız:

async function main() {
  let response = await fetch("name.txt");
  let name = await response.text();
  console.log("Hello, %s!", name);
}

Senkronize görünse de her await, temelde geri çağırma işlevleri için söz dizimi şekeridir:

function main() {
  return fetch("name.txt")
    .then(response => response.text())
    .then(name => console.log("Hello, %s!", name));
}

Bu biraz daha net olan şekersiz örnekte, bir istek başlatılır ve ilk geri çağırma ile yanıtlara abone olunur. Tarayıcı ilk yanıtı (yalnızca HTTP üstbilgileri) aldıktan sonra bu geri çağırma işlevini eşzamansız olarak çağırır. Geri çağırma, response.text() kullanarak gövdeyi metin olarak okumaya başlar ve başka bir geri çağırma ile sonuca abone olur. Son olarak fetch tüm içerikleri aldıktan sonra, konsola "Merhaba, (kullanıcı adı)" yazdıran son geri çağırma işlevini çağırır.

Bu adımların asenkron yapısı sayesinde orijinal işlev, G/Ç planlandıktan sonra kontrolü tarayıcıya döndürebilir ve G/Ç arka planda yürütülürken kullanıcı arayüzünün tamamını duyarlı ve oluşturma, kaydırma vb. diğer görevler için kullanılabilir durumda bırakabilir.

Son örnek olarak, bir uygulamayı belirli bir süre bekleten "sleep" gibi basit API'ler de G/Ç işleminin bir biçimidir:

#include <stdio.h>
#include <unistd.h>
// ...
printf("A\n");
sleep(1);
printf("B\n");

Elbette, mevcut ileti dizisini süre dolana kadar engelleyecek çok basit bir şekilde çevirebilirsiniz:

console.log("A");
for (let start = Date.now(); Date.now() - start < 1000;);
console.log("B");

Aslında Emscripten, "sleep" işlevinin varsayılan uygulamasında tam olarak bunu yapar. Ancak bu yöntem çok verimsizdir, kullanıcı arayüzünün tamamını engeller ve bu sırada başka hiçbir etkinliğin işlenmesine izin vermez. Genellikle üretim kodunda bunu yapmayın.

Bunun yerine, JavaScript'te "sleep" işlevinin daha doğal bir versiyonu setTimeout() işlevini çağırmayı ve bir işleyiciyle abone olmayı içerir:

console.log("A");
setTimeout(() => {
    console.log("B");
}, 1000);

Tüm bu örnekler ve API'ler arasında ortak olan nedir? Her iki durumda da, orijinal sistem dilinde bulunan idiomatik kodda G/Ç için engelleyen bir API kullanılırken web için eşdeğer bir örnekte bunun yerine asenkron bir API kullanılır. Web'e derleme yaparken bu iki yürütme modeli arasında bir şekilde dönüşüm yapmanız gerekir. WebAssembly'de henüz bunu yapabilen yerleşik bir özellik yoktur.

Asyncify ile boşluğu doldurma

İşte bu noktada Asyncify devreye girer. Asyncify, Emscripten tarafından desteklenen ve programın tamamını duraklatıp daha sonra ayarsız olarak devam ettirmenize olanak tanıyan bir derleme zamanı özelliğidir.

Asyncify&#39;nin, eşzamansız görevin sonucunu tekrar WebAssembly&#39;e bağladığı bir JavaScript -> WebAssembly -> web API -> eşzamansız görev çağrısını açıklayan bir çağrı grafiği

Emscripten ile C / C++'ta kullanım

Son örnekte zaman uyumsuz bir uyku uygulamak için Asyncify'i kullanmak isterseniz bunu aşağıdaki gibi yapabilirsiniz:

#include <stdio.h>
#include <emscripten.h>

EM_JS(void, async_sleep, (int seconds), {
    Asyncify.handleSleep(wakeUp => {
        setTimeout(wakeUp, seconds * 1000);
    });
});

puts("A");
async_sleep(1);
puts("B");

EM_JS, JavaScript snippet'lerinin C işlevleri gibi tanımlanmasına olanak tanıyan bir makrodur. İçinde, Emscripten'e programı askıya almasını söyleyen ve asenkron işlem tamamlandıktan sonra çağrılması gereken bir wakeUp() işleyici sağlayan bir Asyncify.handleSleep() işlevi kullanın. Yukarıdaki örnekte işleyici setTimeout()'e iletiliyor ancak geri çağırma işlevlerini kabul eden diğer tüm bağlamlarda kullanılabilir. Son olarak, normal sleep() veya diğer senkron API'ler gibi async_sleep()'ü istediğiniz yerden çağırabilirsiniz.

Bu tür kodları derlediğinizde Emscripten'e Asyncify özelliğini etkinleştirmesini söylemeniz gerekir. Bunu yapmak için -s ASYNCIFY ve -s ASYNCIFY_IMPORTS=[func1, func2] ile birlikte eşzamansız olabilecek işlevlerin dizi benzeri bir listesini iletin.

emcc -O2 \
    -s ASYNCIFY \
    -s ASYNCIFY_IMPORTS=[async_sleep] \
    ...

Bu, Emscripten'in bu işlevlere yapılan çağrıların durumu kaydetmeyi ve geri yüklemeyi gerektirebileceğini bilmesini sağlar. Böylece derleyici, bu tür çağrıların etrafına destekleyici kod ekler.

Artık bu kodu tarayıcıda çalıştırdığınızda, beklediğiniz gibi sorunsuz bir çıkış günlüğü görürsünüz. B, A'dan kısa bir süre sonra gelir.

A
B

Asyncify işlevlerinden de değer döndürebilirsiniz. Yapmanız gereken, handleSleep() sonucunu döndürmek ve sonucu wakeUp() geri çağırma işlevine iletmektir. Örneğin, bir dosyadan okumak yerine uzak bir kaynaktan bir sayı almak istiyorsanız istek göndermek, C kodunu askıya almak ve yanıt gövdesi alındıktan sonra devam etmek için aşağıdaki gibi bir snippet kullanabilirsiniz. Tüm bunlar, çağrı senkronizeymiş gibi sorunsuz bir şekilde yapılır.

EM_JS(int, get_answer, (), {
     return Asyncify.handleSleep(wakeUp => {
        fetch("answer.txt")
            .then(response => response.text())
            .then(text => wakeUp(Number(text)));
    });
});
puts("Getting answer...");
int answer = get_answer();
printf("Answer is %d\n", answer);

Hatta fetch() gibi Promise tabanlı API'ler için geri çağırma tabanlı API'yi kullanmak yerine Asyncify'yi JavaScript'in async-await özelliğiyle birleştirebilirsiniz. Bunun için Asyncify.handleSleep() yerine Asyncify.handleAsync() numaralı telefonu arayın. Ardından, wakeUp() geri çağırma işlevi planlamak yerine bir async JavaScript işlevi iletebilir ve içinde await ve return kullanabilirsiniz. Böylece, ayarsız G/Ç'nin avantajlarından hiçbirini kaybetmeden kodu daha da doğal ve senkronize hale getirebilirsiniz.

EM_JS(int, get_answer, (), {
     return Asyncify.handleAsync(async () => {
        let response = await fetch("answer.txt");
        let text = await response.text();
        return Number(text);
    });
});

int answer = get_answer();

Karmaşık değerler bekleniyor

Ancak bu örnekte yine de yalnızca sayılarla sınırlısınız. Bir dosyasındaki kullanıcı adını dize olarak almaya çalıştığım orijinal örneği uygulamak isterseniz ne olur? Bunu da yapabilirsiniz.

Emscripten, JavaScript ile C++ değerleri arasında dönüşümler yapmanıza olanak tanıyan Embind adlı bir özellik sağlar. Asyncify için de destek sunar. Böylece, harici Promise'larda await()'ü çağırabilirsiniz. Bu işlev, async-await JavaScript kodundaki await gibi çalışır:

val fetch = val::global("fetch");
val response = fetch(std::string("answer.txt")).await();
val text = response.call<val>("text").await();
auto answer = text.as<std::string>();

Bu yöntemi kullanırken ASYNCIFY_IMPORTS zaten varsayılan olarak dahil edildiği için derleme işareti olarak iletmeniz bile gerekmez.

Tamam, tüm bunlar Emscripten'de mükemmel çalışıyor. Diğer araç zincirleri ve diller için ne yapabilirim?

Diğer dillerdeki kullanım

Rust kodunuzda, web'deki bir asenkron API ile eşlemek istediğiniz benzer bir senkron çağrınız olduğunu varsayalım. Bunu da yapabilirsiniz.

Öncelikle, bu tür bir işlevi extern bloğu (veya seçtiğiniz dilin yabancı işlevler için söz dizimi) aracılığıyla normal bir içe aktarma olarak tanımlamanız gerekir.

extern {
    fn get_answer() -> i32;
}

println!("Getting answer...");
let answer = get_answer();
println!("Answer is {}", answer);

Ardından kodunuzu WebAssembly olarak derleyin:

cargo build --target wasm32-unknown-unknown

Artık WebAssembly dosyasını, yığını depolamak/geri yüklemek için kodla donatmanız gerekir. C/C++ için Emscripten bunu bizim için yapar ancak burada kullanılmadığından süreç biraz daha manueldir.

Neyse ki Asyncify dönüştürme işlemi tamamen araç zincirine bağlı değildir. Hangi derleyiciyle üretildiğine bakılmaksızın, rastgele WebAssembly dosyalarını dönüştürebilir. Dönüşüm, Binaryen araç setindeki wasm-opt optimize edici kapsamında ayrı olarak sağlanır ve şu şekilde çağrılabilir:

wasm-opt -O2 --asyncify \
      --pass-arg=asyncify-imports@env.get_answer \
      [...]

Dönüşümü etkinleştirmek için --asyncify'ü iletin ve ardından program durumunun askıya alınması ve daha sonra devam ettirilmesi gereken, virgül ile ayrılmış bir eşzamansız işlev listesi sağlamak için --pass-arg=…'ü kullanın.

Geriye kalan tek şey, WebAssembly kodunu askıya alıp devam ettiren destekleyici çalışma zamanı kodu sağlamaktır. Yine de C / C++ durumunda bu, Emscripten tarafından eklenir. Ancak artık rastgele WebAssembly dosyalarını işleyebilecek özel bir JavaScript yapıştırma koduna ihtiyacınız var. Bunun için bir kütüphane oluşturduk.

Bu aracı https://github.com/GoogleChromeLabs/asyncify adresindeki GitHub'da veya npm'de asyncify-wasm adıyla bulabilirsiniz.

Standart bir WebAssembly başlatma API'sini kendi ad alanının altında simüle eder. Tek fark, normal bir WebAssembly API'sinde yalnızca eşzamanlı işlevleri içe aktarma olarak sağlayabilmeniz, Asyncify sarmalayıcısında ise eşzamansız içe aktarma da sağlayabilmenizdir:

const { instance } = await Asyncify.instantiateStreaming(fetch('app.wasm'), {
    env: {
        async get_answer() {
            let response = await fetch("answer.txt");
            let text = await response.text();
            return Number(text);
        }
    }
});

await instance.exports.main();

Yukarıdaki örnekte get_answer() gibi bir asenkron işlevi WebAssembly tarafından çağırmaya çalıştığınızda kitaplık, döndürülen Promise değerini algılar, WebAssembly uygulamasının durumunu askıya alır ve kaydeder, söz konusu söze abone olur ve söz çözüldükten sonra çağrı yığınını ve durumu sorunsuz bir şekilde geri yükler ve hiçbir şey olmamış gibi yürütmeye devam eder.

Modüldeki herhangi bir işlev, asenkron çağrıda bulunabileceğinden tüm dışa aktarma işlemleri de potansiyel olarak asenkron hale gelir ve bu nedenle sarmalanır. Yukarıdaki örnekte, yürütmenin ne zaman gerçekten sona erdiğini öğrenmek için instance.exports.main() sonucunu await etmeniz gerektiğini fark etmiş olabilirsiniz.

Tüm bu süreç nasıl işler?

Asyncify, ASYNCIFY_IMPORTS işlevlerinden birine yapılan bir çağrıyı algıladığında, bir asenkron işlem başlatır, çağrı yığını ve geçici yerel değişkenler dahil olmak üzere uygulamanın tüm durumunu kaydeder ve daha sonra bu işlem tamamlandığında tüm belleği ve çağrı yığınını geri yükler ve program hiç durmamış gibi aynı yerden ve aynı durumda devam eder.

Bu, daha önce gösterdiğim JavaScript'teki async-await özelliğine oldukça benzer ancak JavaScript'teki özelliğin aksine, dilden özel bir söz dizimi veya çalışma zamanı desteği gerektirmez. Bunun yerine, derleme zamanında düz eşzamanlı işlevleri dönüştürerek çalışır.

Daha önce gösterilen zaman uyumsuz uyku örneğini derleyin:

puts("A");
async_sleep(1);
puts("B");

Asyncify bu kodu alıp yaklaşık olarak aşağıdaki gibi dönüştürür (sözde kod, gerçek dönüşüm bundan daha karmaşıktır):

if (mode == NORMAL_EXECUTION) {
    puts("A");
    async_sleep(1);
    saveLocals();
    mode = UNWINDING;
    return;
}
if (mode == REWINDING) {
    restoreLocals();
    mode = NORMAL_EXECUTION;
}
puts("B");

Başlangıçta mode NORMAL_EXECUTION olarak ayarlanır. Buna karşılık, bu tür dönüştürülmüş kod ilk kez yürütüldüğünde yalnızca async_sleep()'e kadar olan kısım değerlendirilir. Eşzamansız işlem planlanır planlanmaz Asyncify tüm yerel değişkenleri kaydeder ve her işlevden en tepeye dönerek yığını çözer. Böylece kontrolü tarayıcı etkinlik döngüsüne geri verir.

Ardından, async_sleep() çözüldükten sonra Asyncify destek kodu mode'u REWINDING olarak değiştirir ve işlevi tekrar çağırır. Bu sefer, "normal yürütme" dalı atlanır (çünkü iş son seferde zaten yapılmıştır ve "A"yı iki kez yazdırmaktan kaçınmak isterim) ve bunun yerine doğrudan "geri sarma" dalına gidilir. Bu noktaya ulaşıldığında, depolanan tüm yerel değişkenler geri yüklenir, mod "normal" olarak değiştirilir ve kod hiç durdurulmamış gibi yürütmeye devam edilir.

Dönüşüm maliyetleri

Maalesef Asyncify dönüşümü tamamen ücretsiz değildir. Tüm bu yerel değişkenleri depolamak ve geri yüklemek, farklı modlarda çağrı yığınında gezinmek vb. için oldukça fazla destek kodu eklemesi gerekir. Yalnızca komut satırında asenkron olarak işaretlenen işlevleri ve bu işlevleri çağıran tüm işlevleri değiştirmeye çalışır ancak kod boyutu yükü, sıkıştırmadan önce yaklaşık% 50'ye ulaşabilir.

Hassas ayarlanmış koşullarda% 0&#39;a yakın, en kötü durumlarda% 100&#39;ün üzerinde olmak üzere çeşitli karşılaştırmalar için kod boyutu yükü gösteren grafik

Bu durum ideal değildir ancak alternatif olarak işlevin tamamen olmaması veya orijinal kodda önemli ölçüde yeniden yazma yapılması gerektiğinde çoğu durumda kabul edilebilir.

Daha da artmasını önlemek için nihai derlemelerde optimizasyonları her zaman etkinleştirdiğinizden emin olun. Dönüşümleri yalnızca belirtilen işlevlerle ve/veya yalnızca doğrudan işlev çağrılarıyla sınırlandırarak yükü azaltmak için Asyncify'ye özgü optimizasyon seçeneklerini de kontrol edebilirsiniz. Ayrıca çalışma zamanında performans açısından küçük bir maliyet söz konusudur ancak bu maliyet, asynkron çağrılarla sınırlıdır. Ancak gerçek çalışmanın maliyetine kıyasla genellikle önemsizdir.

Gerçek hayattan örnekler

Basit örneklere göz attığınıza göre daha karmaşık senaryolara geçebiliriz.

Makalenin başında da belirtildiği gibi, web'deki depolama seçeneklerinden biri eşzamansız bir File System Access API'dir. Bir web uygulamasından gerçek ana makine dosya sistemine erişim sağlar.

Öte yandan, konsolda ve sunucu tarafında WebAssembly G/Ç için WASI adlı bir de facto standart vardır. Sistem dilleri için bir derleme hedefi olarak tasarlanmıştır ve her türlü dosya sistemi ve diğer işlemleri geleneksel senkronize biçimde gösterir.

Bunları birbirine eşleyebilseydiniz ne olurdu? Ardından, WASI hedefini destekleyen herhangi bir araç zinciriyle herhangi bir kaynak dilinde yazılmış herhangi bir uygulamayı derleyebilir ve gerçek kullanıcı dosyalarında çalışmasını sağlarken web'de bir korumalı alanda çalıştırabilirsiniz. Asyncify ile tam da bunu yapabilirsiniz.

Bu demoda, Rust coreutils paketini WASI'de birkaç küçük düzeltmeyle derledim, Asyncify dönüştürme aracıyla ilettim ve JavaScript tarafında WASI'den File System Access API'ye eşzamansız bağlantılar uyguladım. Xterm.js terminal bileşeniyle birlikte kullanıldığında, tıpkı gerçek bir terminal gibi tarayıcı sekmesinde çalışan ve gerçek kullanıcı dosyalarında çalışan gerçekçi bir kabuk sağlar.

https://wasi.rreverser.com/ adresinden canlı olarak izleyebilirsiniz.

Asyncify kullanım alanları yalnızca zamanlayıcılar ve dosya sistemleriyle sınırlı değildir. Daha da ileri gidip web'de daha özel API'ler kullanabilirsiniz.

Örneğin, Asyncify'nin de yardımıyla libusb'i (USB cihazlarla çalışmak için muhtemelen en popüler yerel kitaplık) web'deki bu tür cihazlara eşzamansız erişim sağlayan bir WebUSB API ile eşlemek mümkündür. Haritalandırıp derledikten sonra, seçili cihazlarda doğrudan bir web sayfasının korumalı alanında çalıştırılacak standart libusb testleri ve örnekleri elde ettim.

Bağlı Canon kamerayla ilgili bilgileri gösteren, bir web sayfasındaki libusb hata ayıklama çıkışının ekran görüntüsü

Ancak bu muhtemelen başka bir blog yayını için hazırlanmış bir hikayedir.

Bu örnekler, Asyncify'nin boşluğu doldurmak ve her türlü uygulamayı web'e taşımak için ne kadar güçlü olabileceğini gösterir. Bu sayede, işlevselliği kaybetmeden platformlar arası erişim, korumalı alan ve daha iyi güvenlik elde edebilirsiniz.