同步独立回调结果

时间:2016-01-08 23:18:21

标签: javascript jquery asynchronous synchronization promise

我正在寻找一种优雅的方法来同步以未知顺序调用的独立回调结果。

function callback1() {
  var result;
};
function callback2() {
  var result;
};

//When done then call
function success(res1, res2) {
  // do whatever
}

我知道我可以这样做:

var res = {};
var dfd = $.Deferred();
function callback1() {
  var result;
  res.res1 = result;
  (res.res1 && res.res2) && (dfd.resolve(res));
};

function callback1() {
  var result;
  res.res2 = result;
  (res.res1 && res.res2) && (dfd.resolve(res));
};

dfd.done(function(result){
  // do whatever
});

但如果有人提出更优雅的解决方案,我将不胜感激

3 个答案:

答案 0 :(得分:1)

如果你返回promises(内置承诺,而不是jQuery延迟)并且你不关心订单,那么你可以使用Promise.all

function callback1() {
  return Promise.resolve(1)
}

function callback2() {
  return Promise.resolve(2)
}

var ps = [callback1(), callback2()]

function add(x, y) {
  return x + y
}

Promise.all(ps).then(function(result) {
  return result.reduce(add)
}).then(console.log) // => 3

如果你想对它们进行排序,你可以这样做,你可以应用一个curried函数,该函数需要尽可能多的参数作为解决的promises,将它提升到promise世界。换句话说:

function apply(pa, pf) {
  return pf.then(function(f) {
    return pa.then(f)
  })
}

function lift(f, ps) {
  return ps.reduce(function(pa, pb) {
    return apply(pb, pa)
  }, Promise.resolve(f))
}

function add(x) {
  return function(y) {
    return x + y
  }
}

lift(add, ps).then(console.log) //=> 3

你也可以通过以下方式对它们进行排序,使得你不需要一个curried函数,首先在数组中收集结果然后减少它:

function sequence(ps) {
  return ps.reduceRight(function(pa, pb) {
    return pa.then(function(a) {
      return pb.then(function(b) {
        return [b].concat(a)
      })
    })
  }, Promise.resolve([]))
}

function add(x, y) {
  return x + y
}

// This looks similar to the Promise.all approach
// but they run in order
sequence(ps).then(function(result) {
  return result.reduce(add)
}).then(console.log) // => 3

答案 1 :(得分:1)

有些图书馆可以执行此操作,例如async库,但是这里有一个"从头开始"解。我也避免承诺避免压倒你,但你应该真正阅读它们,因为它们是最优雅的解决方案,虽然对于初次使用者来说很复杂。

function runInParallel(jobs, done) {
  // Store all our results in an array.
  var results = [];
  // If one job fails, set this to true and use it to
  // ignore all job results that follow.
  var failureOccurred = false;
  // Iterate over each of our registered jobs.
  jobs.forEach(function (runJob, index) {
    // Create a jobDone callback to pass to the job.
    var jobDone = function (err, result) {
      // If another job failed previously, abort processing
      // this job's result. We no longer care.
      if (failureOccurred) return;
      // If this job passed in an error, set failure to true
      // and pass the error to the final done callback.
      if (err) {
        failureOccurred = true;
        done(err);
        return;
      }
      // If we made it this far then push the job result into
      // the results array at the same position as the job in
      // the jobs array.
      results[index] = result;
      // If the results array contains as many results as the
      // jobs array had jobs then we have finished processing
      // them all. Invoke our done callback with an array of
      // all results.
      if (results.length === jobs.length) {
        done(null, results);
      }
    };
    // Begin the job and pass in our jobDone callback.
    runJob(jobDone);
  });
}

这将调用数组中的所有作业函数,并在完成后调用作业应调用的jobDone回调函数。如果任何作业传递错误,则函数将立即调用带有错误的结果回调并忽略其他所有内容。如果作业成功,那么您将获得与作业阵列中的作业位置相同的作业结果数组。只需修改您的工作职能即可接受jobDone回调。

var jobs = [
  function job1(done) {
    try {
      var result;
      done(null, result);
    } catch (err) {
      done(err);
    }
  },
  function job2(done) {
    try {
      var result;
      done(null, result);
    } catch (err) {
      done(err);
    }
  }
];

runInParallel(jobs, function (err, results) {
  if (err) {
    console.error(err);
    return;
  }
  // results[0] = jobs[0] result
  // results[1] = jobs[1] result
  // etc...
});

您可以修改此代码以接受具有属性名称的对象,而不是作业数组。然后,您可以使用相同的属性名称将结果分配给对象,而不是将结果分配到与jobs数组中的作业相同的位置。

示例(这次没有评论):

function runInParallel(jobs, done) {
  var results = {};
  var failureOccurred = false;
  Object.keys(jobs).forEach(function (jobName) {
    var jobDone = function (err, result) {
      if (failureOccurred) return;
      if (err) {
        failureOccurred = true;
        done(err);
        return;
      }
      results[jobName] = result;
      if (results.length === jobs.length) {
        done(null, results);
      }
    };
    jobs[jobName](jobDone);
  });
}

然后你可以像这样消费它:

var jobs = {
  job1: function (done) {
    try {
      var result;
      done(null, result);
    } catch (err) {
      done(err);
    }
  },
  job2: function (done) {
    try {
      var result;
      done(null, result);
    } catch (err) {
      done(err);
    }
  }
};

runInParallel(jobs, function (err, results) {
  if (err) {
    console.error(err);
    return;
  }
  // results.job1 = job1 result
  // results.job2 = job2 result
  // etc...
});

异步库中的parallel function几乎完全符合我们上面所做的。它甚至像我们一样接受一系列工作或命名工作的对象:)

答案 2 :(得分:1)

假设您的任务(callback1()callback2())是同步,您可以选择以函数的形式编写问题中代码的可重用概括返回一个函数,在闭包中捕获几个私有变量:

function resultAggregator(n, fn) {
    var results = {},
        count = 0;
    return function(id, res) {
        count++;
        results[id] = res;
        if (count == n) {
            fn(results);
        }
    }
}

因此,在调用resultAccumulator()之后,您将拥有一个可以保留在其他函数范围内或传递给代码库其他部分的函数。除了它们是同步导出之外,它不对假设或结果的性质做出任何假设。在n结果发布后,它将触发回调。

var myResults = resultAggregator(2, function(results) {
    // do whatever;
});

//The following commands may be in different parts of your code base
myResults('id1', synchTask1());
...
myResults('id2', synchTask2());
...
myResults('id3', synchTask3());

//The second tasks to deliver its data (ostensibly `synchTask1()` and `synchTask2()`, but not necessarily) will trigger the callback.

<强> Demo

这只是执行结果聚合的一种方法。您可能会根据具体情况执行不同的操作。这是一个略有不同的公式,记录了结果的到达顺序:

<强> Demo

无论您编写什么,Deferreds / Promises都不是聚合同步派生数据所必需的。

但是,如果任何一个任务是异步的,或者可能是异步的,那么您可能需要一个承诺聚合器,例如jQuery.when()Promise.all(),在模式的某个位置。