5

JavaScript 回调函数

 11 months ago
source link: https://www.myfreax.com/javascript-callback/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
JavaScript 回调函数

JavaScript 回调函数

在本教程中,您将了解 JavaScript 回调函数,包括同步和异步回调,异步回调的错误处理以及理解什么是回调黑洞。

你也将会知道为什么 JavaScript 存在 promisesasync/await 函数。

什么是回调

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

代码如何运行的。

  • 首先,定义 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));

结果是一样的。但是,你可以传递任何函数,只要该函数接受一个参数并返回布尔值给 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));

根据定义,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);

在此示例中,我们将匿名函数传递给 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);

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

同步回调在高阶函数执行时运行。在上面的例子中 isOddisEven  是同步回调,因为它门在 filter() 函数运行期间也同步运行。

异步回调是在高阶函数执行之后运行的回调函数,异步意味着如果 JavaScript 不会等待操作的完成,它将在等待期间运行其余代码。

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

假设你需要开发一个程序,从远程服务器下载一张图片,并在下载完成后进行处理:

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

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

download(url);
process(picture);

但是,从远程服务器下载图片需要时间,具体取决于网络速度和图片大小。我们定义下面的 download() 函数使用 setTimeout() 函数来模拟网络请求:

function download(url) {
    setTimeout(() => {
        // 下载图片的代码
        console.log(`Downloading ${url} ...`);
    },1000);
}

而下面这段代码模拟 process() 函数的实现:

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

当你执行下面的代码时:

let url = 'https://www.myfreax.com/pic.jpg';

download(url);
process(url);

您将获得以下输出:

Processing https://www.myfreax.com/pic.jpg
Downloading https://www.myfreax.com/pic.jpg ...

这不是您所期望的,因为 process() 函数会 download() 函数之前运行。正确的顺序应该是:

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

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

function download(url, callback) {
    setTimeout(() => {
        // 下载图片的代码
        console.log(`Downloading ${url} ...`);
        
        // 下载完成后处理图片
        callback(url);
    }, 1000);
}

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

let url = 'https://www.myfreax.com/pic.jpg';
download(url, process);
Downloading https://www.myfreax.com/pic.jpg ...
Processing https://www.myfreax.com/pic.jpg

现在,它按预期工作。在此示例中,process() 是传递给异步函数的回调。

当您在异步操作之后使用回调继续执行代码时,回调称为异步回调。为了使代码更简洁,可以将 process() 函数定义为匿名函数:

function download(url, callback) {
    setTimeout(() => {
        // 下载图片的代码
        console.log(`Downloading ${url} ...`);
        // 下载完成后处理图片
        callback(url);

    }, 1000);
}

let url = 'https://www.javascripttutorial.net/pic.jpg';
download(url, function(picture) {
    console.log(`Processing ${picture}`);
}); 

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

嵌套回调和回调黑洞

如何下载三张图片并依次处理?一种典型的方法是在回调函数中调用函数,如下所示:

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

const url1 = 'https://www.myfreax.com/pic1.jpg';
const url2 = 'https://www.myfreax.com/pic2.jpg';
const url3 = 'https://www.myfreax.com/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}`);
    });
  });
});
Downloading https://www.myfreax.com/pic1.jpg ...
Processing https://www.myfreax.com/pic1.jpg
Downloading https://www.myfreax.com/pic2.jpg ...
Processing https://www.myfreax.com/pic2.jpg
Downloading https://www.myfreax.com/pic3.jpg ...
Processing https://www.myfreax.com/pic3.jpg

此代码工作得很好。然而,当复杂性显着增加时,这种回调策略无法很好地扩展。

在回调中嵌套许多异步函数被称为回调黑洞

asyncFunction(function(){
    asyncFunction(function(){
        asyncFunction(function(){
            asyncFunction(function(){
                asyncFunction(function(){
                    ....
                });
            });
        });
    });
});

为避免回调黑洞,您可以使用 promisesasync/await 函数。

  • 回调是作为稍后执行的参数传递给另一个函数的函数。
  • 高阶函数是接受另一个函数作为参数的函数。
  • 回调函数可以是同步的或异步的。

微信公众号

支付宝打赏

myfreax 淘宝打赏

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK