为什么将JavaScript回调作为参数传递?

时间:2019-02-18 15:36:14

标签: javascript asynchronous callback

我正在尝试第一次理解回调。在我看到的所有示例中,回调总是作为参数传递。这是一个常见的示例:

let result = 0;

function add(num1, num2, callback) {
    setTimeout(() => {
        result = num1 + num2;
        callback();
    }, 2000);
}

function logResult() {
    console.log(result);
}

add(4, 5, logResult); // here's the callback passed as argument

使用以下代码可以获得相同的结果。而且它不需要将回调作为参数传递。

let result = 0;

function add(num1, num2) {
    setTimeout(() => {
        result = num1 + num2;
        logResult();
    }, 2000);
}

function logResult() {
    console.log(result);
}

add(4, 5);

将它们作为参数传递只是为了提高可读性和更好地理解代码吗?还是我想念的东西?请有人能启发我吗?

5 个答案:

答案 0 :(得分:6)

您的示例中根本不需要回调,您可以这样做:

function add(num1, num2) {
  setTimeout(() => { // thats a callback too, just saying ...
    const result = num1 + num2; // don't leak variables, declare them!
    console.log(result);
  }, 2000);
}

但是编程只不过是创建可重用代码,然后可以将其组成更复杂的程序。因此,您不想将add的使用限制为记录结果,而是可以通过接受回调来完成各种任务:

 add(1, 2, (result) => {
   add(result, 5, (result2) => {
    alert(result2);
   });
});

答案 1 :(得分:3)

  

它不需要将回调作为参数传递。

是的...

setTimeout(() => {

使用箭头函数定义的一个回调传递给setTimeout

(我知道您的意思是函数callback,但这仍然是一个回调并证明了我的观点)


由于setTimeout不是您定义的函数,因此在可能达到的范围内定义函数的唯一方法是使其成为全局函数。

然后,如果您要同时运行两个setTimeout实例,则将第一个回调分配给第一个全局回调,然后将第二个回调分配给第二个回调……好吧……您会被困住。

答案 2 :(得分:3)

回调是一种解耦代码的机制。 例如,假设API的add部分带有第一个代码,我可以编写:

add(4, 5, console.log);
add(4, 5, alert);
add(4, 5, writeOnTheFileSystemIfNodeJS);
add(4, 5, addToTheDOM);

// etc.

这与您的第二个代码是不可能的:它太耦合了,所以我需要使用不同版本的add函数来完成以上所有四个操作:addConsole,{{1} }等。不仅如此:通过回调,您还提供了一种机制来处理您无法预期的逻辑。也许开发人员想将结果添加到canvas元素上,而您没有提供addAlert东西。但是使用回调,即使原则上不是出于此目的而设计的,也可以实现。

但是请注意,如今对于此类操作-一次 发生-您可能会使用Promises,因为它们与await / async的配合非常好可能会多次发生,您可能想使用事件(例如addCanvas)或流-在关闭功能中,由于{{3} }。

答案 3 :(得分:1)

首先,请注意:回调被认为是不良做法。现在,我们有了更好的方法来处理这种事情。作为语言规范的一部分,我们有Promise个。就外部库而言,我们还有Observable个。 在成为规范的一部分之前,Promise是使用 回调构建的,但是为您提供了一种更具可读性的处理方式,尤其是在回调链方面。

非常特别地是因为通常在库代码中使用回调,而不是在您的主代码中使用回调,所以库开发人员正在添加一种方法来为您的行为添加自定义功能。至于在您自己的代码中使用回调...根据应用程序的当前状态以及该函数的调用者,您可能希望使用不同的回调。底线是separation of concerns是您需要熟悉的重要概念。

例如,

function showModal(whichModal) {
  someLibrary.modal(whichModal).show();
  switch (whichModal) {
    case 'createUser':
      someUserLogic();
      break;
    case 'createProject':
      someProjectLogic();
      break;
  }
}

function createUser() {
  showModal('createUser');
}

function createProject() {
  showModal('createProject');
}

vs

function showModal(whichModal, postShowCallback) {
  someLibrary.modal(whichModal).show();
  postShowCallback();
}

function createUser() {
  showModal('createUser', someUserLogic);
}

function createProject() {
  showModal('createProject', someProjectLogic);
}

您可以看到第一个示例很快就会失控,第二个示例可以很好地解决这个问题

答案 4 :(得分:0)

将回调作为参数传递的原因之一是避免范围界定问题。您的示例中的logResult函数可能未在您调用add的任何地方定义。另外,logResult可能会发生突变。请考虑以下内容:

let result = 0;

function add(num1, num2) {
    setTimeout(() => {
        result = num1 + num2;
        logResult();
    }, 2000);
}

function logResult() {
    console.log(result);
}

add(4, 5);

function logResult() {
    console.log(2);
}

在以上代码段中,logResult函数为hoisted,原始版本被覆盖。请注意,尽管在第二个add(4, 5)声明之前调用了logResult,但这还是发生了。

通过接受回调参数可以避免此问题和其他范围问题。

function add(a, b, callback) {
    setTimeout(() => {
        callback(a + b);
    }, 2000);
}

add(4, 5, (result) => setTimeout(() => console.log(result), 2000));

相关问题