JavaScript 回调函数

摘要: 在本教程中,您将了解 JavaScript 回调函数,包括同步和异步回调。

什么是回调函数

在 JavaScript 中,函数是一等公民。因此,您可以将一个函数作为参数传递给另一个函数。

根据定义,回调函数是您作为参数传递给另一个函数的函数,以便稍后执行。

以下定义了一个filter()函数,它接受一个数组,并返回一个包含奇数的新数组。

function filter(numbers) {
  let results = [];
  for (const number of numbers) {
    if (number % 2 != 0) {
      results.push(number);
    }
  }
  return results;
}
let numbers = [1, 2, 4, 7, 3, 5, 6];
console.log(filter(numbers));Code language: JavaScript (javascript)

它是如何工作的。

  • 首先,定义一个接受数字数组并返回包含奇数的新数组的filter()函数。
  • 其次,定义一个包含奇数和偶数的numbers数组。
  • 第三,调用filter()函数从numbers数组中获取奇数,并输出结果。

如果您想返回一个包含偶数的数组,您需要修改filter()函数。为了使filter()函数更通用和可重用,您可以

  • 首先,将if块中的逻辑提取出来,并将其封装在单独的函数中。
  • 其次,将函数作为参数传递给filter()函数。

以下是更新后的代码

function isOdd(number) {
  return number % 2 != 0;
}

function filter(numbers, fn) {
  let results = [];
  for (const number of numbers) {
    if (fn(number)) {
      results.push(number);
    }
  }
  return results;
}
let numbers = [1, 2, 4, 7, 3, 5, 6];
console.log(filter(numbers, isOdd));Code language: JavaScript (javascript)

结果是一样的。但是,您可以将任何接受参数并返回布尔值的函数传递给filter()函数的第二个参数。

例如,您可以使用filter()函数来返回一个包含偶数的数组,如下所示

function isOdd(number) {
  return number % 2 != 0;
}
function isEven(number) {
  return number % 2 == 0;
}

function filter(numbers, fn) {
  let results = [];
  for (const number of numbers) {
    if (fn(number)) {
      results.push(number);
    }
  }
  return results;
}
let numbers = [1, 2, 4, 7, 3, 5, 6];

console.log(filter(numbers, isOdd));
console.log(filter(numbers, isEven));Code language: JavaScript (javascript)

根据定义,isOddisEven是回调函数。因为filter()函数接受一个函数作为参数,所以它被称为高阶函数

回调函数可以是匿名函数,它是一个没有名称的函数,如下所示

function filter(numbers, callback) {
  let results = [];
  for (const number of numbers) {
    if (callback(number)) {
      results.push(number);
    }
  }
  return results;
}

let numbers = [1, 2, 4, 7, 3, 5, 6];

let oddNumbers = filter(numbers, function (number) {
  return number % 2 != 0;
});

console.log(oddNumbers);Code language: JavaScript (javascript)

在本例中,我们将一个匿名函数传递给filter()函数,而不是使用单独的函数。

在 ES6 中,您可以使用箭头函数,如下所示

function filter(numbers, callback) {
  let results = [];
  for (const number of numbers) {
    if (callback(number)) {
      results.push(number);
    }
  }
  return results;
}

let numbers = [1, 2, 4, 7, 3, 5, 6];

let oddNumbers = filter(numbers, (number) => number % 2 != 0);

console.log(oddNumbers);Code language: JavaScript (javascript)

回调函数有两种类型:同步回调函数和异步回调函数。

同步回调函数

同步回调函数在使用回调函数的高阶函数执行期间执行。isOddisEven是同步回调函数的例子,因为它们在filter()函数执行期间执行。

异步回调函数

异步回调函数在使用回调函数的高阶函数执行后执行。

异步意味着,如果 JavaScript 必须等待操作完成,它将在等待时执行其余的代码。

请注意,JavaScript 是一种单线程编程语言。它通过回调队列和事件循环来执行异步操作。

假设您需要开发一个脚本,该脚本从远程服务器下载图片,并在下载完成后处理它。

function download(url) {
    // ...
}

function process(picture) {
    // ...
}

download(url);
process(picture);Code language: JavaScript (javascript)

但是,从远程服务器下载图片需要时间,这取决于网络速度和图片的大小。

以下download()函数使用setTimeout()函数来模拟网络请求

function download(url) {
    setTimeout(() => {
        // script to download the picture here
        console.log(`Downloading ${url} ...`);
    },1000);
}Code language: JavaScript (javascript)

这段代码模拟了process()函数

function process(picture) {
    console.log(`Processing ${picture}`);
}Code language: JavaScript (javascript)

当您执行以下代码时

let url = 'https://tutorial.javascript.ac.cn/pic.jpg';

download(url);
process(url);Code language: JavaScript (javascript)

您将获得以下输出

Processing https://javascripttutorial.net/pic.jpg
Downloading https://javascripttutorial.net/pic.jpg ...Code language: JavaScript (javascript)

这不是您期望的结果,因为process()函数在download()函数之前执行。正确的顺序应该是

  • 下载图片,等待下载完成。
  • 处理图片。

要解决这个问题,您可以将process()函数传递给download()函数,并在下载完成后在download()函数内执行process()函数,如下所示

function download(url, callback) {
    setTimeout(() => {
        // script to download the picture here
        console.log(`Downloading ${url} ...`);
        
        // process the picture once it is completed
        callback(url);
    }, 1000);
}

function process(picture) {
    console.log(`Processing ${picture}`);
}

let url = 'https://wwww.javascripttutorial.net/pic.jpg';
download(url, process);Code language: JavaScript (javascript)

输出

Downloading https://tutorial.javascript.ac.cn/pic.jpg ...
Processing https://tutorial.javascript.ac.cn/pic.jpgCode language: JavaScript (javascript)

现在,它按预期工作了。

在本例中,process()是一个传递给异步函数的回调函数。

当您使用回调函数来在异步操作完成后继续执行代码时,回调函数被称为异步回调函数。

为了使代码更简洁,您可以将process()函数定义为匿名函数

function download(url, callback) {
    setTimeout(() => {
        // script to download the picture here
        console.log(`Downloading ${url} ...`);
        // process the picture once it is completed
        callback(url);

    }, 1000);
}

let url = 'https://tutorial.javascript.ac.cn/pic.jpg';
download(url, function(picture) {
    console.log(`Processing ${picture}`);
}); Code language: JavaScript (javascript)

处理错误

download()函数假设一切正常,并且没有考虑任何异常。以下代码引入了两个回调函数:successfailure,分别用于处理成功和失败情况

function download(url, success, failure) {
  setTimeout(() => {
    console.log(`Downloading the picture from ${url} ...`);
    !url ? failure(url) : success(url);
  }, 1000);
}

download(
  '',
  (url) => console.log(`Processing the picture ${url}`),
  (url) => console.log(`The '${url}' is not valid`)
);
Code language: JavaScript (javascript)

嵌套回调函数和回调地狱

如何下载三张图片并依次处理它们?一种典型的方法是在回调函数内部调用download()函数,如下所示

function download(url, callback) {
  setTimeout(() => {
    console.log(`Downloading ${url} ...`);
    callback(url);
  }, 1000);
}

const url1 = 'https://tutorial.javascript.ac.cn/pic1.jpg';
const url2 = 'https://tutorial.javascript.ac.cn/pic2.jpg';
const url3 = 'https://tutorial.javascript.ac.cn/pic3.jpg';

download(url1, function (url) {
  console.log(`Processing ${url}`);
  download(url2, function (url) {
    console.log(`Processing ${url}`);
    download(url3, function (url) {
      console.log(`Processing ${url}`);
    });
  });
});
Code language: JavaScript (javascript)

输出

Downloading https://tutorial.javascript.ac.cn/pic1.jpg ...
Processing https://tutorial.javascript.ac.cn/pic1.jpg
Downloading https://tutorial.javascript.ac.cn/pic2.jpg ...
Processing https://tutorial.javascript.ac.cn/pic2.jpg
Downloading https://tutorial.javascript.ac.cn/pic3.jpg ...
Processing https://tutorial.javascript.ac.cn/pic3.jpgCode language: JavaScript (javascript)

脚本工作正常。

但是,当复杂性显著增长时,这种回调策略无法很好地扩展。

在回调函数内嵌套多个异步函数被称为回调地狱

asyncFunction(function(){
    asyncFunction(function(){
        asyncFunction(function(){
            asyncFunction(function(){
                asyncFunction(function(){
                    ....
                });
            });
        });
    });
});
Code language: JavaScript (javascript)

为了避免回调地狱,您可以使用Promiseasync/await函数。

摘要

  • 回调函数是作为参数传递给另一个函数的函数,以便稍后执行。
  • 高阶函数是一个接受另一个函数作为参数的函数。
  • 回调函数可以是同步的或异步的。
本教程对您有帮助吗?