Saya percaya bahwa kelanjutan adalah kasus khusus dari callback. Suatu fungsi dapat memanggil balik sejumlah fungsi, berapapun kali. Sebagai contoh:
var array = [1, 2, 3];
forEach(array, function (element, array, index) {
array[index] = 2 * element;
});
console.log(array);
function forEach(array, callback) {
var length = array.length;
for (var i = 0; i < length; i++)
callback(array[i], array, i);
}
Namun jika suatu fungsi memanggil kembali fungsi lain sebagai hal terakhir yang dilakukannya maka fungsi kedua disebut sebagai kelanjutan dari yang pertama. Sebagai contoh:
var array = [1, 2, 3];
forEach(array, function (element, array, index) {
array[index] = 2 * element;
});
console.log(array);
function forEach(array, callback) {
var length = array.length;
// This is the last thing forEach does
// cont is a continuation of forEach
cont(0);
function cont(index) {
if (index < length) {
callback(array[index], array, index);
// This is the last thing cont does
// cont is a continuation of itself
cont(++index);
}
}
}
Jika suatu fungsi memanggil fungsi lain sebagai hal terakhir yang dilakukannya maka itu disebut panggilan ekor. Beberapa bahasa seperti Skema melakukan optimasi panggilan ekor. Ini berarti bahwa panggilan ekor tidak menimbulkan overhead penuh dari panggilan fungsi. Alih-alih itu diterapkan sebagai goto sederhana (dengan bingkai tumpukan fungsi panggilan digantikan oleh bingkai tumpukan panggilan ekor).
Bonus : Melanjutkan ke gaya kelanjutan passing. Pertimbangkan program berikut:
console.log(pythagoras(3, 4));
function pythagoras(x, y) {
return x * x + y * y;
}
Sekarang jika setiap operasi (termasuk penambahan, perkalian, dll.) Ditulis dalam bentuk fungsi maka kita akan memiliki:
console.log(pythagoras(3, 4));
function pythagoras(x, y) {
return add(square(x), square(y));
}
function square(x) {
return multiply(x, x);
}
function multiply(x, y) {
return x * y;
}
function add(x, y) {
return x + y;
}
Selain itu jika kami tidak diizinkan untuk mengembalikan nilai apa pun maka kami harus menggunakan kelanjutan sebagai berikut:
pythagoras(3, 4, console.log);
function pythagoras(x, y, cont) {
square(x, function (x_squared) {
square(y, function (y_squared) {
add(x_squared, y_squared, cont);
});
});
}
function square(x, cont) {
multiply(x, x, cont);
}
function multiply(x, y, cont) {
cont(x * y);
}
function add(x, y, cont) {
cont(x + y);
}
Gaya pemrograman di mana Anda tidak diizinkan untuk mengembalikan nilai (dan karenanya Anda harus menggunakan kelanjutan kelanjutan) disebut gaya kelanjutan kelanjutan.
Namun ada dua masalah dengan gaya passing lanjutan:
- Melewati kelanjutan meningkatkan ukuran tumpukan panggilan. Kecuali jika Anda menggunakan bahasa seperti Skema yang menghilangkan panggilan ekor, Anda akan berisiko kehabisan ruang stack.
- Sungguh menyakitkan menulis fungsi bersarang.
Masalah pertama dapat dengan mudah diselesaikan dalam JavaScript dengan memanggil melanjutkan secara tidak sinkron. Dengan memanggil kelanjutan secara asinkron, fungsi kembali sebelum kelanjutan dipanggil. Karenanya ukuran tumpukan panggilan tidak bertambah:
Function.prototype.async = async;
pythagoras.async(3, 4, console.log);
function pythagoras(x, y, cont) {
square.async(x, function (x_squared) {
square.async(y, function (y_squared) {
add.async(x_squared, y_squared, cont);
});
});
}
function square(x, cont) {
multiply.async(x, x, cont);
}
function multiply(x, y, cont) {
cont.async(x * y);
}
function add(x, y, cont) {
cont.async(x + y);
}
function async() {
setTimeout.bind(null, this, 0).apply(null, arguments);
}
Masalah kedua biasanya diselesaikan dengan menggunakan fungsi call-with-current-continuation
yang sering disingkat callcc
. Sayangnya callcc
tidak dapat sepenuhnya diimplementasikan dalam JavaScript, tetapi kami dapat menulis fungsi penggantian untuk sebagian besar kasus penggunaannya:
pythagoras(3, 4, console.log);
function pythagoras(x, y, cont) {
var x_squared = callcc(square.bind(null, x));
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
}
function square(x, cont) {
multiply(x, x, cont);
}
function multiply(x, y, cont) {
cont(x * y);
}
function add(x, y, cont) {
cont(x + y);
}
function callcc(f) {
var cc = function (x) {
cc = x;
};
f(cc);
return cc;
}
The callcc
Fungsi mengambil fungsi f
dan berlaku ke current-continuation
(disingkat cc
). Ini current-continuation
adalah fungsi kelanjutan yang membungkus seluruh tubuh fungsi setelah panggilan ke callcc
.
Pertimbangkan tubuh fungsi pythagoras
:
var x_squared = callcc(square.bind(null, x));
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
Yang current-continuation
kedua callcc
adalah:
function cc(y_squared) {
add(x_squared, y_squared, cont);
}
Demikian pula yang current-continuation
pertama callcc
adalah:
function cc(x_squared) {
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
}
Karena yang current-continuation
pertama callcc
berisi yang lain, callcc
itu harus dikonversi ke gaya kelanjutan passing:
function cc(x_squared) {
square(y, function cc(y_squared) {
add(x_squared, y_squared, cont);
});
}
Jadi intinya callcc
secara logis mengkonversi seluruh fungsi tubuh kembali ke apa yang kita mulai dari (dan memberikan fungsi-fungsi anonim nama cc
). Fungsi pythagoras menggunakan implementasi callcc ini menjadi:
function pythagoras(x, y, cont) {
callcc(function(cc) {
square(x, function (x_squared) {
square(y, function (y_squared) {
add(x_squared, y_squared, cont);
});
});
});
}
Sekali lagi Anda tidak dapat menerapkan callcc
dalam JavaScript, tetapi Anda dapat menerapkannya gaya meneruskan kelanjutan dalam JavaScript sebagai berikut:
Function.prototype.async = async;
pythagoras.async(3, 4, console.log);
function pythagoras(x, y, cont) {
callcc.async(square.bind(null, x), function cc(x_squared) {
callcc.async(square.bind(null, y), function cc(y_squared) {
add.async(x_squared, y_squared, cont);
});
});
}
function square(x, cont) {
multiply.async(x, x, cont);
}
function multiply(x, y, cont) {
cont.async(x * y);
}
function add(x, y, cont) {
cont.async(x + y);
}
function async() {
setTimeout.bind(null, this, 0).apply(null, arguments);
}
function callcc(f, cc) {
f.async(cc);
}
Fungsi callcc
ini dapat digunakan untuk mengimplementasikan struktur aliran kontrol yang kompleks seperti blok uji coba, coroutine, generator, serat , dll.