Javascript异步函数组合

Javascript异步函数组合,javascript,functional-programming,Javascript,Functional Programming,我有几个异步函数,每个函数的参数数量不同,最后一个参数是回调。我想把这些按顺序排列。比如说 function getData(url, callback){ } function parseData(data, callback){ } 通过使用以下命令: Function.prototype.then = function(f){ var ff = this; return function(){ ff.apply(null, [].slice.call(arguments).c

我有几个异步函数,每个函数的参数数量不同,最后一个参数是回调。我想把这些按顺序排列。比如说

function getData(url, callback){
}
function parseData(data, callback){
}
通过使用以下命令:

Function.prototype.then = function(f){ 
  var ff = this; 
  return function(){ ff.apply(null, [].slice.call(arguments).concat(f)) } 
}
可以这样调用这些函数,并将输出打印到console.log

getData.then(parseData.then(console.log.bind(console)))('/mydata.json');
我一直试图改用这种语法,但无法正确使用Then函数。有什么想法吗

getData.then(parseData).then(console.log.bind(console))('/mydata.json');

实现一个函数或库,使您能够像上面那样链接方法,这是一项非常重要的任务,需要付出大量的努力。上述示例的主要问题是持续的上下文更改-在没有内存泄漏的情况下很难管理调用链的状态(即,将对所有链接函数的引用保存到模块级变量->GC将永远无法从内存中释放函数)

如果您对这种编程策略感兴趣,我强烈建议您使用现有的、已建立且经过良好测试的库,如或。我个人推荐前者,因为它试图尽可能接近ECMAScript 6的规范


出于教育目的,我建议您看看Promise库内部是如何工作的——我相信通过检查它的源代码并使用它,您会学到很多东西。

我对这个问题有一些想法,并创建了以下代码,这些代码有点符合您的要求。不过,我知道这个概念还远远不够完美。代码和下面对原因进行了注释

Function.prototype.\u thenify={
队列:[],
然后:函数(nextOne){
//将项目推送到队列中
这个.\u thenify.queue.push(nextOne);
归还这个;
},
移交:职能(){
//将数据移交给下一个函数,在同一上下文中调用它(这样我们就不会失去队列)
this.\u thenify.queue.shift().apply(this,arguments);
归还这个;
}
}
Function.prototype.then=Function(){返回此值。\u thenify.then.apply(此值为参数)};
Function.prototype.handlover=Function(){返回此值。\ n验证.handlover.apply(此值为参数)};
函数getData(json){
//模拟异步调用
setTimeout(function(){getData.handOver(json,'params from getData');},10);
//我们不能调用此.handlovery(),因为每个函数调用都会创建一个新的上下文
//这意味着您必须这样做,或者将from getData的上下文绑定到函数本身
//这意味着每次调用函数时,您都有相同的上下文
}
函数parseData(){
//模拟异步调用
setTimeout(function(){parseData.handOver('params from parseData');},10);
//这里我们可以使用它。因为在getData的上下文中调用了parseData
//为了清楚起见,我就让它这样
}
获取数据
.then(function(){console.log(arguments);this.handlover();})//看看我们如何在这里使用它
.then(解析数据)
.then(console.log)('/mydata.json');//在这里,我们实际上是从调用函数开始链
//要在getData上下文中调用该链(这样您就可以始终执行此操作。handOver()),请执行以下操作:
//获取数据
//.then(函数(){console.log(参数);this.handlover();})
//.then(解析数据)

//.then(console.log).bind(getData)('/mydata.json')问题在于
然后
返回当前函数的包装,连续的链式调用将再次包装它,而不是包装上一个回调。实现这一点的一种方法是使用闭包并覆盖
,然后在每次调用时覆盖

Function.prototype.then=函数(f){
var ff=这个;
函数wrapCallback(previousCallback,callback){
var wrapper=function(){
previousCallback.apply(null,[].slice.call(arguments).concat(callback));
};
ff.then=包装器。then=函数(f){
callback=wrapCallback(callback,f);//一个新的链式调用,因此包装回调
返回ff;
}
返回包装器;
}
return ff=wrapCallback(this,f);//用包装器“替换”原始函数并返回
}
/*
*范例
*/ 
函数getData(json,回调){
setTimeout(函数(){callback(json)},100);
}
函数parseData(数据,回调){
回调(数据“Hello”);
}
函数doSomething(数据、文本、回调){
回调(文本);
}
函数printData(数据){
console.log(数据);//应打印“Hello”
}
获取数据
.then(解析数据)
.然后(做某事)
.then(printData)('/mydata.json')是正确的。但我愿意回答纯粹出于学术目的

让我们将代码简化为:

Function.prototype.then = function (callback){ 
  var inner = this;
  return function (arg) { return inner(arg, callback); }
}
以及:

让我们分析每个函数的类型:

function chainAsync(seed, functions, callback) {
    if (functions.length === 0) callback(seed);
    functions[0](seed, function (value) {
        chainAsync(value, functions.slice(1), callback);
    });
}
chainAsync("someurl", [getData, parseData], function (data) {
    console.log(data);
});
  • getData
    (字符串、函数(参数等))→ 空
  • 函数(参数,函数)。然后
    就是
    (函数(参数,…)→ 函数(参数)
这是问题的核心。当您这样做时:

getData.then(function(argument){}
它实际返回一个类型为
function(argument)
的函数。这就是为什么
.then
不能被调用到它上面,因为
.then
期望被调用到
函数(参数,函数)
类型上

您要做的是包装回调函数。(对于
getData.then(parseData).then(f)
,您希望用
f
包装
parseData
,而不是
getData.then(parseData)
的结果

以下是我的解决方案:

Function.prototype.setCallback = function (c) { this.callback = c; }
Function.prototype.getCallback = function () { return this.callback; }

Function.prototype.then = function (f) {
  var ff = this;
  var outer = function () {
     var callback = outer.getCallback();
     return ff.apply(null, [].slice.call(arguments).concat(callback));
  };

  if (this.getCallback() === undefined) {
    outer.setCallback(f);
  } else {
    outer.setCallback(ff.getCallback().then(f));
  }

  return outer;
}

这看起来是该对象的一个很好的用途。Promission通过为异步计算提供公共接口来提高回调函数的可重用性。Promission允许您将函数的异步部分封装在Promission对象中,而不是让每个函数都接受回调参数。然后您可以使用Promission方法(Promise.all、Promise.prototype.then)来链接异步HRO
// Instead of accepting both a url and a callback, you accept just a url. Rather than
// thinking about a Promise as a function that returns data, you can think of it as
// data that hasn't loaded or doesn't exist yet (i.e., promised data).
function getData(url) {
    return new Promise(function (resolve, reject) {
        // Use resolve as the callback parameter.
    });
}
function parseData(data) {
    // Does parseData really need to be asynchronous? If not leave out the
    // Promise and write this function synchronously.
    return new Promise(function (resolve, reject) {
    });
}
getData("someurl").then(parseData).then(function (data) {
    console.log(data);
});

// or with a synchronous parseData
getData("someurl").then(function (data) {
    console.log(parseData(data));
});
function chainAsync(seed, functions, callback) {
    if (functions.length === 0) callback(seed);
    functions[0](seed, function (value) {
        chainAsync(value, functions.slice(1), callback);
    });
}
chainAsync("someurl", [getData, parseData], function (data) {
    console.log(data);
});