美文网首页
延续(continuation)和回调(callback)之间的

延续(continuation)和回调(callback)之间的

作者: pipu | 来源:发表于2019-12-30 16:10 被阅读0次

延续(continuation)和回调(callback)之间的区别

我相信延续是回调的一个特例。一个函数可以回调任意数量的函数,任意的次数调用。例如:

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

然而如果一个函数把回调另一个函数作为其最后执行的部分,那么第二个函数就称为第一个的延续。例如:

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;

    cont(0);// forEach 函数最后一步执行cont,cont就是forEach的延续

    function cont(index) {
        if (index < length) {
            callback(array[index], array, index);

            cont(index++); // cont 方法的最后一步, 所以cont是它自己的延续

        }

    }
}

如果一个函数在最后调用另外一个函数,那么这种方式称为尾调用。类似Schema这类的语言会进行尾调用优化,这意味着尾调用不会导致整个函数调用的开销。取而代之的是继续的实现方式(正在执行的函数的栈结构被尾部调用的函数的栈结构替代)

附录: 继续进行延续的传递格式,让我们来瞧下面的程序:


console.log(pythagoras(3, 4));
function pythagoras(x, y) {
    return x * x + y * y;
}

如果每个操作(包括 加 乘)都被写成函数形式:

console.log(pythagoras(3, 4));
function pythagoras(x, y) {
    return add(sqare(x), sqare(y));
}
function sqare(x) {
    return multiply(x, x)
}
function multiply(x, y) {
    return x * y;
}
function add(x, y) {
    return x + y;
}

此外,如果我们不允许函数返回任何值那么我们必须使用延续就像:

pythagoras(3, 4, console.log);
function pythagoras(x, y, cont) {
    square(x, function (square_x) {
        square(y, function (square_y) {
            add(square_x, square_y, 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);
}

这种不允许返回计算值(并且必须调整调用顺序来完成延续)的编程格式,被称为延续格式。

延续格式存在两个问题:

  1. 传递延续代码会增加调用栈的空间。除非你正在使用一种类似Schema的消除尾调用的语言,否则你会冒栈溢出的风险。
  2. 写嵌套代码让人烦躁

第一个问题在js中通过异步调用延续可以很简单的解决。通过异步调用延续,函数在延续被调用之前返回值。因此栈空间没有增加。

Function.prototype.async = async;

pythagoras.async(3, 4, console.log);

function pythagoras(x, y, cont) {
    square.async(x, function (square_x) {
        square.async(y, function (square_y) {
            add.async(square_x, square_y, 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);
}

ps: 通过异步调用,使嵌套的函数在执行的时候,外层嵌套的函数会在执行完成后将结果交给异步执行器句柄(timer),由timer来将结果带入回调中调用而不是在栈中等待所有的内部函数调用完成。

第二个问题通常通过被称为 call-with-cuttent-continuation(callcc)来解决。可惜callcc在js中不能完全被实现,但是我们可以写出一种解决其大部分使用场景的替代方法:

pythagoras(3, 4, console.log);

function pythagoras(x, y, cont) {
    var x_square = callcc(square.bind(null, x));
    var y_square = callcc(square.bind(null, y));
    add(x_square, y_square, 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
}

callcc 函数接受一个函数类型参数f并且传入current-continuation(简称cc)调用它,current-continuation是一个延续函数在callcc被调用后包裹函数体剩下的部分

来看下pythagoras函数体:

    var x_square = callcc(square.bind(null, x));
    var y_square = callcc(square.bind(null, y));
    add(x_square, y_square, cont);

第二个callcccurrent-continuation函数是:

function cc(y_square) {
    add(x_square, y_square, cont);
}

类似的,第一个callcccurrent-continuation是:

function cc(x_square) {
    var y_square = callcc(square.bind(null, y));
    add(x_square, y_square, cont);
}

因为第一个callcccurrent-continuation包含另外一个callcc,它必须转换成延续格式:

function cc(x_squared) {
    square(y, function cc(y_squared) {
        add(x_squared, y_squared, cont);
    });
}

所以实质上callcc逻辑上将整个函数体经转换又回到最开始的样子(并且给那些匿名函数命名为cc).pythagoras 使用这种实现方式的callcc 变成:

function pythagoras(x, y, cont) {
    callcc(function(cc) {
        square(x, function (x_squared) {
            square(y, function (y_squared) {
                add(x_squared, y_squared, cont);
            });
        });
    });
}

虽然在js中不能够实现callcc,但是可以在延续风格中实现它如:

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

什么是callcc?
为什么在current-continuation中调用另一个callcc需要将该callcc转化成continuation风格?
如果js中不能实现call,那么最后的代码对于解决嵌套代码书写的意义何在?

参考链接

What's the difference between a continuation and a callback?

相关文章

  • 延续(continuation)和回调(callback)之间的

    延续(continuation)和回调(callback)之间的区别 我相信延续是回调的一个特例。一个函数可以回调...

  • 将CallBack改写成Promise

    CallBack回调函数是js的特色之一, 但CallBack回调方法, 非常容易造成回调地狱(callback ...

  • Callback、Promise、async/await

    一、Callback回调函数例子: callback函数应用举例: 就是通过callback回调函数来传回数据 缺...

  • kashgari学习笔记-1

    1、回调函数的使用 使用了两个回调函数,eval_callback和tf_board_callback。 1、ev...

  • 回调

    1. 同步回调 Callback Server Client 运行结果 2. 异步回调 Callback同上 Se...

  • 回调(callback)

    回调(callback)就是讲一段可执行的代码和一个特定的事件绑定起来。当特定的事件发生时,就会执行这段代码。Ob...

  • 回调Callback

    回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其...

  • 回调--CallBack

    回调是一个编程技巧,用于解决顺序流程无法解决或者说使用顺序流程来解决会十分麻烦的问题 比如: 场景一:在打游戏的时...

  • 回调callback

    title: 回调callback 参考: JAVA回调机制(CallBack)详解一个经典例子让你彻彻底底理解j...

  • 回调(CallBack)

    觉得比较好的一篇关于回调的文章https://zhuanlan.zhihu.com/p/88434924[http...

网友评论

      本文标题:延续(continuation)和回调(callback)之间的

      本文链接:https://www.haomeiwen.com/subject/gqemoctx.html