Bahasa dikompilasi ke JS - cara paling elegan untuk melakukan menunggu gaya sinkron

8

Saya mencoba membuat (lagi) bahasa yang mengkompilasi ke JavaScript. Salah satu fitur yang saya ingin miliki adalah kemampuan untuk melakukan operasi async JavaScript secara sinkron (tidak persis sinkron - tanpa memblokir utas utama, tentu saja). Lebih sedikit bicara, lebih banyak contoh:

/* These two snippets should do exactly the same thing */

// the js way
var url = 'file.txt';
fetch(url)
    .then(function (data) {
        data = "(" + data + ")";
        return sendToServer(data);
    }).then(function (response) {
        console.log(response);
    });

// synchronous-like way
var url = 'file.txt';
var response = sendToServer("(" + fetch(url) + ")");
console.log(response);

Apa cara paling elegan untuk mengkompilasinya kembali ke JS?

Kriteria, diurutkan berdasarkan kepentingan:

  1. Performa
  2. Kompatibilitas mundur
  3. Kode yang dapat dibaca (tidak terlalu penting)

Ada beberapa cara yang mungkin untuk mengimplementasikannya, ketiganya muncul di pikiran saya:

  1. Menggunakan Janji:

    • f(); a( b() ); akan berubah menjadi
    • f().then(function(){ return b() }).then(function(x){ a(x) });
    • pro: relatif mudah diimplementasikan, polyfillable kembali ke ES3
    • kontra: membuat fungsi baru dan instance Proxy untuk setiap panggilan fungsi
  2. Menggunakan generator :

    • f(); a( b() ); akan berubah menjadi
    • yield f(); tmp = yield b(); yield a( tmp );
    • pro: javascript lebih bagus
    • kontra: membuat proxy, generator dan iterator pada setiap langkah, juga tidak bisa diisi polyfillable
    • EDIT: sebenarnya mereka bisa diisi dengan polyfillable menggunakan recompiler lain (mis. Regenerator )
  3. Menggunakan XMLHttpRequest sinkron:

    • meminta skrip server yang menunggu hingga panggilan lain dari tempat lain dalam kode
    • pro: de facto tidak ada perubahan dalam kode, super mudah diimplementasikan
    • kontra: memerlukan skrip server (=> tanpa portabilitas, khusus browser); selanjutnya, XMLHttpRequest sinkron memblokir utas sehingga tidak berfungsi sama sekali
    • EDIT: Ini sebenarnya akan bekerja di dalam Pekerja

Masalah utama adalah bahwa seseorang tidak dapat mengetahui apakah suatu fungsi tidak sinkron sampai runtime. Itu menghasilkan dua solusi yang setidaknya bekerja jauh lebih lambat daripada JS murni. Jadi saya bertanya:

Apakah ada cara yang lebih baik untuk diterapkan?

Dari jawaban:

Menunggu kata kunci (@ MI3Guy)

  • C # way (mana yang bagus !)
  • memperkenalkan kata kunci baru (yang dapat disamarkan sebagai fungsi (warga negara kelas 1) yang mis. melempar jika programmer mencoba untuk meneruskannya sebagai argumen)
  • Bagaimana cara mengetahui fungsi itu asinkron? Tidak ada kata kunci lain!
    • Setiap fungsi yang mengandung awaitkata kunci menjadi tidak sinkron?
    • Ketika kode sampai await, itu mengembalikan janji? Lain memperlakukan sebagai fungsi biasa?
m93a
sumber
11
synchronously (without blocking the thread, of course) Anda tetap menggunakan kata itu. Saya tidak berpikir itu berarti apa yang Anda pikirkan artinya, karena secara serempak berarti "memblokir utas".
Mason Wheeler
2
Baca lebih lanjut tentang kelanjutan dan gaya meneruskan kelanjutan
Basile Starynkevitch
1
@MasonWheeler: Saya menganggap op berarti dia ingin menggunakan sintaks gaya sinkron.
Brian
Saya tidak akan menyebut ini sebagai jawaban, tetapi apa yang Anda coba terdengar agak seperti apa yang C # lakukan dengan fungsi async-nya. Saya merasa metode yang paling konsisten dengan nilai pengembalian adalah penggunaan janji, dan ya, ini akan melibatkan banyak fungsi anonim. Anda dapat mencoba menemukan artikel tentang bagaimana kode C # mengkompilasi untuk referensi; itu mungkin sama tidak efisiennya dalam beberapa hal.
Katana314
2
f(); a( b() );akan berubah menjadi f().then(b).then(a);Anda tidak menyertakan fungsi.
theouroureye

Jawaban:

3

Dengan asumsi bahasa diketik secara dinamis, saya dapat melihat dua cara berbeda ini dapat ditangani tanpa mengetahui pada waktu kompilasi jika panggilan adalah async.

Salah satu pendekatan akan mengasumsikan setiap panggilan bisa async. Namun, ini akan sangat tidak efisien dan menghasilkan banyak kode tambahan. Ini pada dasarnya adalah apa yang Anda lakukan pada opsi 2.

Pilihan lain adalah menggunakan serat. Serat seperti benang ringan yang dijadwalkan oleh program. Serat itu sendiri memutuskan kapan harus menyerahkan kontrol. Namun, ini memerlukan dukungan sistem operasi. Sejauh yang saya tahu, satu-satunya cara untuk menggunakan serat dalam JavaScript adalah dalam node.js. Dugaan saya adalah bahwa jika Anda mengkompilasi ke JS bahwa tujuan (atau setidaknya tujuan) dijalankan pada browser yang mengesampingkan opsi ini. Ini akan menjadi versi opsi 3 yang lebih ringan.

Jujur, saya akan mempertimbangkan untuk menambahkan kata kunci seperti C # 's await. Ini memiliki sejumlah keunggulan selain menspesifikasikan bahwa suatu fungsi adalah async. Fungsi dapat dipanggil untuk menghasilkan masa depan tanpa menunggu mereka segera. Ini memungkinkan beberapa operasi async terjadi sekaligus daripada berurutan. Selain itu, lebih baik untuk eksplisit tentang operasi apa yang async karena kode lain dapat berjalan saat operasi berlangsung. Jika async otomatis, mungkin mengejutkan ketika beberapa data yang Anda gunakan dimodifikasi oleh potongan kode eksternal.

Dalam C # dalam metode yang ditandai sebagai async, pernyataan kembali digunakan untuk mengembalikan nilai yang akan berada di dalam masa depan, bukan masa depan itu sendiri. Perhatikan, bagaimanapun, bahwa pengubah async tidak benar-benar bagian dari tanda tangan metode. awaitdapat digunakan dengan metode apa pun yang mengembalikan masa depan.

Jika Anda tidak ingin menambahkan pengubah async untuk fungsi, Anda dapat memutuskan (seperti yang Anda nyatakan) bahwa setiap fungsi dengan menunggu diperlakukan seperti memiliki pengubah async implisit. Jadi, bahkan jika suatu fungsi memiliki await, tetapi kembali sebelum mencapainya, fungsi tersebut masih harus mengembalikan masa depan. Saya juga akan merekomendasikan memaparkan beberapa cara untuk mengubah nilai menjadi masa depan yang lengkap yang berisi nilai, terutama jika tidak ada cara untuk melakukannya secara implisit dengan menambahkan pengubah async.

MI3Guy
sumber
Tujuan awal saya adalah menciptakan bahasa yang benar-benar konsisten tanpa struktur yang tidak dapat digunakan kembali (mis. Jika ada if(){} else{}, pemrogram juga dapat mendefinisikan struktur serupa seperti ifZero(){} elseWhile(){}). Tapi sepertinya saya harus memperkenalkan fungsi menunggu global yang melempar (atau setidaknya mengembalikan tidak terdefinisi) jika programmer mencoba untuk menetapkan nilainya ke variabel (fungsi adalah warga negara kelas 1).
m93a
Memperbarui pertanyaan (paling bawah). Bisakah Anda memberi tahu saya apa pendapat Anda tentang pertanyaan di sana? Terima kasih.
m93a
Saya menambahkan rincian lebih lanjut berdasarkan pembaruan Anda. Beri tahu saya jika Anda bermaksud sesuatu yang berbeda.
MI3Guy