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

Ingvar Stepanyan
Ingvar Stepanyan

Web'deki G/Ç API'leri eşzamansızdır ancak çoğu sistem dilinde eşzamanlıdır. WebAssembly'de kod derlerken bir API türünü bir başka türe bağlamanız gerekir. Bu köprü Asyncify'dır. Bu yayında, Asyncify'ın ne zaman, nasıl kullanılacağını ve arka planda nasıl çalıştığını öğreneceksiniz.

Sistem dillerinde G/Ç

C'deki basit bir örnekle başlayacağım. Diyelim ki bir dosyadan kullanıcının adını okumak ve onu "Merhaba, (kullanıcı adı)!" mesajıyla karşılamak istiyorsunuz:

#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 bir şey ifade etmese de, her boyuttan bir uygulamada bulabileceğiniz bir şeyi zaten göstermektedir: Dış dünyadan bazı girişleri okur, bunları dahili olarak işler ve çıktıları tekrar dış dünyaya yazar. Dış dünyayla bu tür tüm etkileşimler, yaygın olarak giriş-çıkış işlevleri adı verilen ve G/Ç olarak kısaltılan birkaç işlev aracılığıyla gerçekleşir.

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

Bu işlevler ilk bakışta oldukça basit görünür ve verileri okumak veya yazmak için gereken makineler hakkında iki kez düşünmek zorunda kalmazsınız. Ancak ortama bağlı olarak içeride de pek çok etkinlik olabilir:

  • Giriş dosyası yerel bir sürücüde bulunuyorsa uygulamanın, dosyayı bulmak için bir dizi bellek ve disk erişimi gerçekleştirmesi, izinleri kontrol etmesi, okuma için açması ve ardından, istenen bayt sayısı alınıncaya kadar blok bazında okuma yapması gerekir. Bu işlem, diskinizin hızına ve istenen boyuta bağlı olarak oldukça yavaş olabilir.
  • Alternatif olarak, giriş dosyası eklenmiş bir ağ konumunda bulunuyor olabilir. Bu durumda, ağ yığını da sürece dahil olur ve bu da her bir işlemin karmaşıklığını, gecikmesini ve potansiyel deneme sayısını artırır.
  • Son olarak, printf bile konsola bir şeyler yazdıracağı garanti edilmez ve bir dosyaya veya ağ konumuna yönlendirilebilir. Bu durumda, yukarıdaki adımların aynısını uygulaması gerekir.

Özetle, G/Ç yavaş olabilir ve koda hızlıca göz atarak belirli bir çağrının ne kadar süreceğini tahmin edemezsiniz. Bu işlem devam ederken uygulamanızın tamamı donmuş olarak görünür ve kullanıcıya yanıt vermez.

Bu, C veya C++ ile de sınırlı değildir. Çoğu sistem dili, tüm G/Ç'yi eşzamanlı API'ler biçiminde sunar. Örneğin, örneği Rust'a çevirirseniz API daha basit görünebilir ancak aynı ilkeler geçerlidir. Sadece bir çağrı yaparsınız ve eşzamanlı olarak sonucun geri dönmesini beklersiniz. Bu sırada tüm pahalı işlemleri gerçekleştirir ve sonunda tek bir çağrıyla sonucu döndürür:

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

Peki bu örneklerden herhangi birini WebAssembly'de derleyip web'e çevirmeye çalıştığınızda ne oluyor? Ya da belirli bir örnek vermek gerekirse "dosya okuma" işleminin anlamı nedir? Bir miktar depolama alanındaki verilerin okunması gerekir.

Web'in eşzamansız modeli

Web'de, bellek içi depolama (JS nesneleri), localStorage, IndexedDB, sunucu tarafı depolama ve yeni bir File System Access API gibi farklı depolama seçenekleri mevcuttur.

Ancak bu API'lerden yalnızca ikisi (bellek içi depolama alanı ve localStorage) eşzamanlı olarak kullanılabilir ve her ikisi de depolayabileceğiniz öğeler ve saklama süresi konusunda en sınırlayıcı seçeneklerdir. Diğer tüm seçenekler yalnızca eşzamansız API'ler sağlar.

Bu, web'de kod yürütmenin temel özelliklerinden biridir: G/Ç içeren tüm zaman alan 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 herhangi bir kullanıcı kodunun, kullanıcı arayüzü ile aynı iş parçacığında çalışması gerekmesidir. CPU süresi 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şlatabilmesini ve bitene kadar sekmenin tamamını veya geçmişte milisaniyelerle birkaç saniye arasında kalan her şeyi engelleyebilmesini istemezsiniz.

Bunun yerine, kodun yalnızca iş bittiğinde yürütülecek bir geri çağırma ile birlikte bir G/Ç işlemi planlamasına izin verilir. Bu tür geri çağırmalar, tarayıcının etkinlik döngüsünün bir parçası olarak yürütülür. Burada ayrıntılara girmeyeceğim, ancak etkinlik döngüsünün nasıl işlediğini öğrenmek isterseniz bu konuyu derinlemesine açıklayan Görevler, mikro görevler, sıralar ve programlar bölümüne göz atabilirsiniz.

Kısacası, tarayıcı tüm kod parçalarını sıradan birer birer alarak sonsuz bir döngü halinde çalıştırır. Bir etkinlik tetiklendiğinde, tarayıcı ilgili işleyiciyi sıraya alır ve bir sonraki döngü yinelemesinde sıradan çıkarılarak yürütülür. Bu mekanizma, yalnızca tek bir iş parçacığı kullanırken eşzamanlılık simülasyonu yapmaya ve çok sayıda paralel işlem çalıştırmaya olanak tanır.

Bu mekanizma hakkında unutulmaması gereken önemli bir nokta, özel JavaScript (veya WebAssembly) kodunuz çalışırken etkinlik döngüsünün engellenmesi ve bu sırada da harici işleyici, etkinlik, G/Ç vb. öğelere tepki vermenin mümkün olmadığıdır. G/Ç sonuçlarını geri almanın tek yolu bir geri çağırma kaydetmek, kodunuzu yürütmeyi bitirmek ve kontrolü, bekleyen görevlerin sürdürülebilmesi için tekrar tarayıcıya vermektir. G/Ç tamamlandıktan sonra işleyiciniz bu görevlerden biri haline gelir ve yürütülür.

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

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

Eşzamanlı görünse de, temelde her await geri çağırma için temelde söz dizimidir:

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

Biraz daha anlaşılır olan bu şekeri ayrıştırılmış örnekte, bir istek başlatılır ve yanıtlar ilk geri çağırma ile abone olur. Tarayıcı ilk yanıtı (yalnızca HTTP üst bilgilerini) aldıktan sonra, eşzamansız olarak bu geri çağırmayı çağırır. Geri çağırma, response.text() kullanarak metni 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 "Hello, (username)!" ifadesini yazdıran son geri çağırmayı çağırır.

Bu adımların eşzamansız doğası sayesinde, orijinal işlev, G/Ç planlandığı anda kontrolü tarayıcıya döndürebilir ve G/Ç arka planda çalışırken kullanıcı arayüzünün tamamını duyarlı ve oluşturma, kaydırma gibi diğer görevler için kullanılabilir durumda bırakabilir.

Son bir örnek vermek gerekirse, bir uygulamanın belirli sayıda saniye beklemesini sağlayan "sleep" gibi basit API'ler bile bir G/Ç işlemi biçimidir:

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

Elbette, geçerli iş parçacığını süre dolana kadar engelleyecek şekilde çok basit bir şekilde çevirebilirsiniz:

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

Aslında, Emscripten'ın varsayılan "uyku" uygulamasında yaptığı şey tam olarak budur, ancak bu çok verimsizdir, kullanıcı arayüzünün tamamını engeller ve bu sırada başka etkinliklerin işlenmesine izin vermez. Genellikle bunu üretim kodunda yapmayın.

Bunun yerine, JavaScript'te "uyku"nun daha deyimsel bir versiyonu setTimeout() çağrısını ve bir işleyiciyle abone olmayı içerir:

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

Tüm bu örnekler ve API'lerde ortak olan nedir? Her durumda, orijinal sistem dilindeki deyimsel kod, G/Ç için bir engelleme API'si kullanırken web'e karşılık gelen bir örnek, bunun yerine eşzamansız bir API kullanır. Web'de derleme yaparken bu iki yürütme modeli arasında bir şekilde dönüşüm yapmanız gerekir ve WebAssembly'nin henüz bunu yapma konusunda yerleşik bir özelliği yoktur.

Asyncify ile boşluğu doldurun

Asyncify burada devreye girer. Asyncify, Emscripten tarafından desteklenen bir derleme zamanı özelliğidir ve programın tamamının duraklatılıp daha sonra eşzamansız olarak devam ettirilmesine olanak tanır.

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

Emscripten ile C / C++ kanallarında kullanım

Son örnekte, eşzamansız bir uyku uygulamak için Asyncify'ı 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'lerini C işlevleriymiş gibi tanımlamayı sağlayan bir makrodur. İçinde, Emscripten'a programı askıya almasını söyleyen ve eşzamansız işlem tamamlandıktan sonra çağrılması gereken bir wakeUp() işleyicisi sağlayan Asyncify.handleSleep() işlevini kullanın. Yukarıdaki örnekte, işleyici setTimeout() öğesine iletilir ancak geri çağırmaları kabul eden diğer bağlamlarda kullanılabilir. Son olarak, normal sleep() veya diğer herhangi bir eşzamanlı API gibi istediğiniz yerde async_sleep() çağrısı yapabilirsiniz.

Bu tür bir kodu derlerken Emscripten'a Eşzamansız özelliğini etkinleştirmesini bildirmeniz gerekir. Bunu, eşzamansız olabilecek işlev dizisine benzer bir diziyle birlikte -s ASYNCIFY ve -s ASYNCIFY_IMPORTS=[func1, func2] öğelerini belirterek yapabilirsiniz.

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

Böylece Emscripten, bu işlevlere yapılan tüm çağrıların durumun kaydedilip geri yüklenmesini gerektirebileceğini bilir. Böylece derleyici, bu tür çağrıların etrafına destekleyici kod ekler.

Şimdi, tarayıcıda bu kodu çalıştırdığınızda, beklediğiniz gibi sorunsuz bir çıkış günlüğü görürsünüz. B, A'dan sonra kısa bir gecikmenin ardından B'ye 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ğırmasına iletmektir. Örneğin, bir dosyadan okuma yapmak yerine uzak bir kaynaktan numara getirmek isterseniz aşağıdaki gibi bir snippet kullanarak istek gönderebilir, C kodunu askıya alabilir ve yanıt gövdesi alındığında devam ettirebilirsiniz. Tüm işlemler, çağrı eşzamanlı yapılmış 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ğırmaya dayalı API kullanmak yerine Asyncify'ı JavaScript'in eşzamansız bekleme özelliğiyle birleştirebilirsiniz. Bunun için Asyncify.handleSleep() yerine Asyncify.handleAsync() numaralı telefonu arayın. Ardından, bir wakeUp() geri çağırma işlemi planlamak zorunda kalmadan, async JavaScript işlevi iletebilir ve içeride await ve return öğelerini kullanabilirsiniz. Böylece, kodun daha doğal ve eşzamanlı görünmesini sağlarken eşzamansız G/Ç'nin avantajlarından hiçbir şey kaybetmezsiniz.

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 sizi yalnızca sayılarla sınırlamaya devam edersiniz. Bir kullanıcının adını dosyadan dize olarak almaya çalıştığım orijinal örneği uygulamak isterseniz ne olur? Bunu siz de yapabilirsiniz!

Emscripten, JavaScript ve C++ değerleri arasındaki dönüşümleri yönetmenize olanak tanıyan Embind adlı bir özellik sunar. Asyncify için de destek vardır. Böylece harici Promise'larda await() çağrısı yapabilirsiniz. Böylece, eşzamansız-await JavaScript kodunda await işleviyle aynı işleve sahiptir:

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, zaten varsayılan olarak eklendiği için ASYNCIFY_IMPORTS öğesini derleme işareti olarak aktarmanız bile gerekmez.

Emscripten'da tüm bunlar harika çalışıyor. Peki ya diğer araç zincirleri ve diller?

Diğer dillerdeki kullanım

Rust kodunuzun bir yerinde web'deki eşzamansız bir API ile eşlemek istediğiniz benzer bir eşzamanlı çağrınızın olduğunu varsayalım. Bunu siz de yapabilirsiniz!

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

extern {
    fn get_answer() -> i32;
}

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

Kodunuzu WebAssembly'de derleyin:

cargo build --target wasm32-unknown-unknown

Şimdi, yığını depolamak/geri yüklemek için WebAssembly dosyasını kodla eklemeniz gerekir. C / C++ için Emscripten bunu bizim için yapar, ancak burada kullanılmaz. Bu yüzden süreç biraz daha elle yapılır.

Neyse ki, Asyncify dönüşümü tamamen araç zincirinden bağımsızdır. Hangi derleyici tarafından üretildiğine bakılmaksızın rastgele WebAssembly dosyalarını dönüştürebilir. Dönüşüm, İkili araç zincirinden wasm-opt optimize edicinin bir parçası olarak 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ınıp daha sonra devam ettirileceği eşzamansız işlevlerin virgülle ayrılmış bir listesini sağlamak için --pass-arg=… kullanın.

Geriye sadece bunu yapacak destekleyici çalışma zamanı kodunu sağlamak kaldı: WebAssembly kodunu askıya alma ve devam ettirme. C / C++ örneğinde de Emscripten tarafından eklenir, ancak şimdi rastgele WebAssembly dosyalarını işleyecek özel JavaScript birleştirici koduna ihtiyacınız var. Bunun için bir kitaplık oluşturduk.

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

Standart bir WebAssembly örneklendirme API'sini simüle eder ancak kendi ad alanı altındadır. Tek fark, normal WebAssembly API'sinde içe aktarma olarak yalnızca eşzamanlı işlevleri sağlayabilir, Asyncify sarmalayıcısı altında ise eşzamansız içe aktarma işlemleri de sağlayabilirsiniz:

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();

Bu tür bir eşzamansız işlevi (yukarıdaki örnekteki get_answer() gibi) WebAssembly tarafından çağırmayı denediğinizde, kitaplık döndürülen Promise öğesini tespit eder, WebAssembly uygulamasının durumunu askıya alır ve kaydeder, taahhüt tamamlamaya abone olur ve daha sonra, bu işlem çözümlendikten sonra çağrı yığınını ve durumunu sorunsuz bir şekilde geri yükler ve hiçbir şey olmamış gibi yürütmeye devam eder.

Modüldeki herhangi bir işlev eşzamansız çağrı oluşturabileceğinden, tüm dışa aktarma işlemleri de potansiyel olarak eşzamansız hale gelir ve bu nedenle sarmalanır. Yukarıdaki örnekte, yürütme işleminin gerçekten ne zaman tamamlandığını öğrenmek için instance.exports.main() işleminin sonucunu await yapmanız gerektiğini fark etmiş olabilirsiniz.

Tüm bunlar arka planda nasıl çalışıyor?

Asyncify, ASYNCIFY_IMPORTS işlevlerinden birine bir çağrı algıladığında eşzamansız bir işlem başlatır, çağrı yığını ve tüm geçici yereller de dahil olmak üzere uygulamanın tüm durumunu kaydeder. Daha sonra, bu işlem tamamlandığında tüm bellek ve çağrı yığınını geri yükler ve program hiç durmamış gibi aynı yerden devam eder.

Bu, JavaScript'te önceden gösterdiğim eşzamansız bekleme özelliğine oldukça benzer; ancak JavaScript'in aksine, bu 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 eşzamansız uyku örneğini derlerken:

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

Asyncify, bu kodu alır ve aşağı yukarı aşağıdakine benzer (sahte 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 ayarlanmıştır. Benzer şekilde, bu tür dönüştürülen kod ilk kez yürütüldüğünde yalnızca async_sleep() işlemine kadar giden kısım değerlendirilir. Eşzamansız işlem planlandığı anda, Asyncify tüm yerel kullanıcıları kaydeder ve her işlevden en üste geri dönerek yığını gevşetir. Böylece kontrolü tekrar tarayıcı etkinlik döngüsüne verirsiniz.

Ardından async_sleep() çözümlenirse Asyncify destek kodu, mode değerini REWINDING olarak değiştirip işlevi tekrar çağırır. Bu sefer "normal yürütme" dalı atlanır. Çünkü bu işlem son işini zaten yapmıştır ve "A"nın iki kez yazdırılmasını istemiyorum. Bunun yerine doğrudan "geri sarma" dalına gelir. Ulaşıldığında, depolanan tüm yerelleri geri yükler, modu "normal" olarak değiştirir ve kod en başta hiç durdurulmamış gibi yürütme işlemine devam eder.

Dönüşüm maliyetleri

Maalesef Asyncify dönüşümü tamamen ücretsiz değil. Çünkü tüm bu yerel halkın depolanıp geri yüklenmesi, çağrı yığınında farklı modlarda gezinmek vb. için epey destekleyici bir kod yerleştirmesi gerekiyor. Yalnızca komut satırında eşzamansız olarak işaretlenen işlevleri ve bunların potansiyel çağrılarını değiştirmeye çalışır. Ancak kod boyutu ek yükü, sıkıştırmadan önce yaklaşık% 50'ye varan bir ek yüke sahip olabilir.

Çeşitli karşılaştırmalarda kod boyutu ek yükünü gösteren bir grafik. Bu grafik, ince ayar yapılmış koşullarda yaklaşık% 0&#39;dan en kötü durumlarda% 100&#39;ün üzerine kadardır.

Bu ideal bir yöntem değildir ancak çoğu durumda alternatif, işlevlerin bir arada bulunmadığı veya orijinal kodda önemli değişiklikler yapmak zorunda olmadığı durumlarda kabul edilebilir.

Daha da yüksek bir ilerlemeyi önlemek için son derlemelerde her zaman optimizasyonları etkinleştirin. Dönüşümleri yalnızca belirtilen işlevler ve/veya yalnızca doğrudan işlev çağrılarıyla sınırlandırarak ek yükü azaltmak için Eş zamansız optimizasyon seçeneklerini de işaretleyebilirsiniz. Çalışma zamanı performansının küçük bir maliyeti vardır ancak bu maliyet eşzamansız çağrıların kendisiyle sınırlıdır. Ancak gerçek işin maliyetiyle karşılaştırıldığında bu maliyet genellikle göz ardı edilebilir bir miktardır.

Gerçek demolar

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

Makalenin başında belirtildiği gibi, web'deki depolama seçeneklerinden biri eşzamansız 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ı fiili bir standart vardır. Sistem dilleri için bir derleme hedefi olarak tasarlanmıştır ve geleneksel eşzamanlı bir biçimde her türden dosya sistemini ve diğer işlemleri sunar.

Birini diğeriyle eşleştirebilseydiniz nasıl olurdu? Ardından, WASI hedefini destekleyen herhangi bir araç zinciriyle herhangi bir kaynak dilde uygulamaları derleyebilir ve bu uygulamayı web'deki bir korumalı alanda çalıştırabilir, bu sırada uygulamanın gerçek kullanıcı dosyalarında çalışmasına izin verebilirsiniz. Asyncify ile tam da bunu yapabilirsiniz.

Bu demoda, Rust coreutils kasalarını WASI için birkaç küçük yamayla derledim, Asyncify dönüşümüyle ilettim ve JavaScript tarafında WASI'dan File System Access API'ye eşzamansız bağlamalar uyguladım. Bu API, Xterm.js terminal bileşeniyle birleştirildiğinde tarayıcı sekmesinde çalışan ve tıpkı gerçek bir terminal gibi gerçek kullanıcı dosyalarında çalışan gerçekçi bir kabuk sağlar.

https://wasi.rreverser.com/ adresine giderek göz atın.

Eşzamansız kullanım alanları yalnızca zamanlayıcılar ve dosya sistemleriyle sınırlı değildir. Bununla birlikte web'de daha fazla niş API kullanabilirsiniz.

Örneğin, Asyncify'ın yardımıyla, USB cihazlarıyla çalışmak için muhtemelen en popüler yerel kitaplık olan libusb'ı, web'de bu tür cihazlara eşzamansız erişim sağlayan bir WebUSB API ile eşlemek de mümkündür. Harita oluşturup derledikten sonra, bir web sayfasının korumalı alanında, seçtiğim cihazlarda çalıştırmak için standart libusb testleri ve örnekleri elde ettim.

Bir web sayfasındaki libusb hata ayıklama çıktısının
ekran görüntüsü, bağlı Canon kamera hakkında

Ancak bu büyük olasılıkla başka bir blog yayınının hikayesi olacak.

Bu örnekler, Asyncify'ın boşlukları doldurmak ve her tür uygulamayı web'e taşımak için ne kadar güçlü olabileceğini gösteriyor. Bu sayede, işlevden ödün vermeden platformlar arası erişim, korumalı alan kullanımı ve daha iyi güvenlik elde edebilirsiniz.