服务热线:站内信联系
         

星辉平台开户:教你正确使用Javascript的callback(修正)

时间:2021-10-13 09:05:23 文章作者:星辉平台开户 点击:

每一个javascript的初学者至少一次会面临到这个问题:callback的函数是什么?

我们其实可以从callback这个单词本身了解一些。大概就是说在一次成功的完成任务或者任务失败之后给发起者caller的一个通知。

在本文中,我想更少地关注callback的技术层面的东西,尽可能多地用常用的自然语言来解释他们是如何工作的。也许可以帮助大家理解callback是什么,它为什么会存在。

如果你是一个javascript初学者,这篇文章绝对是你所需要的。

首先,什么是一个function?

在Javascript中一个function就是一组完成一个任务的语句,这组语句可以独立于function之外存在,但是把它们放到一个function中可以使我们在其他的多个地方重复利用。

下面是一个function,将传入的偶数乘以2然后返回结果,如果传入的不是偶数,则返回原值。

function doubleEven(n) {
    if (n % 2 === 0) {
    	return n * 2;
    }
    return n;
}

现在,只要你需要,你可以在很多的地方调用这个function

doubleEven(10); // 输出, 20
doubleEven(5); // 输出, 5
你可以将一个function作为一个参数传递给另一个function

在上面的例子,我们看到,我们可以将一个数值作为一个参数传递给一个function。同样,我们也可以把一个function作为一个参数传递给另一个function。

get_info(function(){
 		console.log('function2 body'); 
 })

值得注意的是,我们将整个function的定义作为参数传递给了function get_info。传入的function没有名字,称之为anonymous function。

什么是callback function?

Javascript语言最重要的一个特征就是它的function可以接受另一个function作为参数的能力。

function的caller调用者可以传入一个function作为参数,然后在需要的时候触发。我们在这里举一个例子。

李雷,一个小男孩,喜欢吃pizza。一天早上他拿起手机,使用Ipizza app下单订了一个pizza。李雷选择了他最爱吃的口味,然后点击下单。

Ipizza接受了订单,告知李雷,将会在pizza准备好并开始派送时通知他。李雷,这个快乐的男孩,等了一会儿,得到了pizza做好并开始派送的通知。

如果我们分解一下这个故事,事件的顺序应该是这样的:

李雷下了订单Ipizza接受订单Ipizza准备pizza,过一定的事件pizza准备好Ipizza通知李雷,pizza开始派送

通知李雷的机理跟callback function类似。

让我们用编程的语言来描写这个故事

上面的顺序事件我们可以在function中用一组语句来实现。

首先,李雷下pizza订单,Ipizza接受订单。

orderPizza("牛肉","芝士酱")

现在,这个orderPizza function在Ipizza服务器上的某个地方运行,并执行以下的逻辑(实际上可能会执行更复杂的逻辑,我们在这里简化):

function orderPizza(type, name) {
    console.log('pizza已下单...');
    console.log('正在准备Pizza');
    setTimeout(function () {
        let msg = `您的 ${type} ${name} Pizza已经准备好! 总金额为 56元`;
        console.log(`服务器上的记录 ${msg}`);
    }, 3000);
}

setTimeout演示了pizza准备需要一些时间。pizza准备好以后再服务器端记录一些信息。但是,出现了一个问题。

message只是记录在了服务器端,可怜的李雷不知道这个消息。我们需要告知他pizza已经准备好了。

callback function的引入

我们需要引入一个callback function,让李雷知道pizza的状态。让我们更改一下orderPizza,传入一个callback function参数。注意一下,我们在pizza准备好以后,带着相关的提示信息调用callback。

function orderPizza(type, name,callback) {
    console.log('pizza已下单...');
    console.log('正在准备Pizza');
    setTimeout(function () {
        let msg = `您的 ${type} ${name} Pizza已经准备好! 总金额为 56元`;
        callback(msg)
    }, 3000);
}

现在,我们来更改一下orderPizza function的调用方式:

orderPizza("牛肉","芝士酱",function(message){
		console.log(message)
})

现在执行的话,orderPizza的caller调用者会在pizza准备好以后收到通知的消息。这个是不是很有用呢?

返回数组中的所有奇数

假设我们现在有一个数组:

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

为了得到数组中的奇数,我们可以使用数组array的filter(),filter根据传入的function参数来比对,将符合条件的数重新组成一个新的数组array.

下面的代码返回true,如果是奇数:

function isOddNumber(number) {
    return number % 2;
}

现在我们可以把isOddNumber作为callback传入array的filter星辉娱乐注册了。

const oddNumbers = numbers.filter(isOddNumber);
console.log(oddNumbers);//输出为[1,7,3,5]

上例中,idOddNumber是一个callback function,当你作为参数传给一个function时,你只需要传递function的参考reference,即函数名称(不带括号对)。

使代码更简化一点,可以用anonymous function作为参数:

let oddNumbers = numbers.filter(function(number) {
    return number % 2;
});
console.log(oddNumbers); // 输出:[ 1, 7, 3, 5 ]

在ES6中,我们可以使用箭头function:

let oddNumbers = numbers.filter(number => number % 2);
同步Synchronous Callback function

如果我们的代码是从上到下按顺序依次执行,则它是同步的。上例的isOddNumber是同步callback function的一个例子。

异步Asynchronous Callback function

Asynchronous异步指的是如果Javascript不得不等待一个操作的完成,在等待的过程中它先执行其他的代码。

假设我们现在需要开发一段代码,从服务器上下载一个图片,下载完成后处理这个图片。

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

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

download(url);
process(picture);

但是,从服务器下载图片是需要时间的,根据图片的大小和网络的质量,时间长短也不一样。

下面的代码使用setTimeout模拟下载过程。

function download(url) {
    setTimeout(() => {
        // 下载图片的代码在这里
        console.log(`正在下载 ${url} ...`);
    }, 3* 1000);
}

下面的代码代表处理一个图片:

function process(picture) {
    console.log(`正在处理图片 ${picture}`);
}

当我们执行如下的代码时:

let url = 'https://www.ilaoxu.cn/foo.jg';
download(url);
process(url);

我们会得到下面的结果:

正在处理图片https://www.ilaoxu.cn/foo.jg
正在下载https://www.ilaoxu.cn/foo.jg

这并不是我们想要的结果,因为在下载图片完成之前就执行了处理图片的function。正确的顺序应该是:

下载一个图片,等待下载完成处理图片

为了解决这个问题,我们可以把process作为callback function传递给download(),在download function中下载完图片再执行process这个callback。

function download(url, callback) {
    setTimeout(() => {
        // 下载图片的代码在这里
        console.log(` 下载图片 ${url} ...`);
        
        // 图片下载完成后在调用传入的callback
        callback(url);
    }, 3000);
}

function process(picture) {
    console.log(`处理图片 ${picture}`);
}

let url = 'http://www.iccset.org/uploads/allimg/20211013/pic.jpg';
download(url, process);

输出如下:

下载图片https://www.ilaoxu.cn/pic.jpg
处理图片https://www.ilaoxu.cn/pic.jpg

现在结果如我们所愿。

在上例中,process是一个callback function传递给一个异步的function。当你在异步操作完成后使用callback来继续执行代码时,这些callback就称为异步callback。

使用异步callback,你可以在不打乱目前整体的任务的情况下,提前预定一个动作。

为了简化代码,我们可以把process写成一个anonymous function。

function download(url, callback) {
    setTimeout(() => {
        // 下载图片的代码在这里
        console.log(`下载图片 ${url} ...`);
        // 图片下载完成后在调用传入的callback
        callback(url);

    }, 3000);
}

let url = 'http://www.iccset.org/uploads/allimg/20211013/pic.jpg';
download(url, function(picture) {
    console.log(`处理图片 ${picture}`);
}); 
嵌套callback和callback hell

我们如何按顺序下载图片和处理图片呢?典型的做法是在callback中再次调用download function。

function download(url, callback) {
    setTimeout(() => {
        //下载图片的代码在这里
        console.log(`下载图片 ${url} ...`);
        // 图片下载完成后在调用传入的callback
        callback(url);
    }, 3000);
}

const url1 = 'http://www.iccset.org/uploads/allimg/20211013/pic1.jpg';
const url2 = 'http://www.iccset.org/uploads/allimg/20211013/pic2.jpg';
const url3 = 'http://www.iccset.org/uploads/allimg/20211013/pic3.jpg';

download(url1,function(picture){
    console.log(`处理图片 ${picture}`);
    // 下载第二张图片
    download(url2,function(picture){
        console.log(`处理图片 ${picture}`);
        // 下载第三张图片
        download(url3,function(picture){
            console.log(`处理图片 ${picture}`);
        });
    });
});

输出结果:

下载图片https://www.ilaoxu.cn/pic1.jpg
处理图片https://www.ilaoxu.cn/pic1.jpg
下载图片https://www.ilaoxu.cn/pic2.jpg
处理图片https://www.ilaoxu.cn/pic2.jpg
下载图片https://www.ilaoxu.cn/pic3.jpg
处理图片https://www.ilaoxu.cn/pic3.jpg

这段代码工作起来没问题,但随着项目越来越复杂,这种callback的策略就很难在随着扩展。

嵌套过多层的callback被称为pyramid of doom或者callback hell

为避免出现这种情况,可以使用Promise()或者async/await function

总结:callback可以是同步的,也可以是异步的。一个Javascript的function可以接受另一个function作为参数。将function作为参数传入另一个function是一个功能强大的编程概念,可以作为当某事发生时,通知调用者的工具。也被称为callback function。你可以根据具体需要使用callback通知调用者。callback同样被用于根据不同的任务执行状态(成功,失败)来执行特定的工作逻辑。需要注意的是-嵌套过多的callback function可能不是一个好主意,可能会导致callback hell。你觉得callback还有什么优点或好的用法吗?


本文由星辉注册【官方首页】编辑发布,转载请注明出处

本文链接:http://iccset.org/article/xinghuiyulexinwen/125.html

【产品推荐】