回头呼喊你的爱情:Callback回呼函式

甚麽是「Callback function」?

MDN的解释如下:

「回呼函式(callback function)是指能藉由参数(argument)通往另一个函式的函式。它会在外部函式内调用、以完成某些事情。」

我改写一下MDN上的范例:

function aFunc(name) {
  alert('Hello ' + name);
}

function bFunc(callback) {
  var name = prompt('报名华山论剑大会,请输入你的名字:');
  callback(name);
}

bFunc(aFunc);

最後呼叫函式 bFunc (aFunc),aFunc是bFunc的callback参数,bFunc的变数name又藉由callback传入bFunc之中。所以我们可以理解「把A函式当成B函式的参数,透过B函式来呼叫它」,A函式就是一个Callback function。

解释有点抽象吗?让我们换个场景,想想之前提过的「事件监听」。

例如:看到红灯,然後踩刹车!踩刹车这个动作,在「看到红灯」这个条件满足的时候才执行。所以我们会监听「看到红灯」这个事件,一旦事件触发,就去呼叫「刹车」这个动作(函式)。这也是把「刹车」这个函式当成事件监听的参数。

还有一个常常会用到的window.setTimeout()也是callback function的经典案例:

window.setTimeout(function(){//do something},1000);

所以我们可以归纳出:

  • 把A函式当成B函式的参数,透过B函式来呼叫它」,A函式就是一个Callback function。
  • 满足某个条件才去执行的函式,就可以称为Callback function。

那在甚麽时候适合使用callback function呢?我想是在「控制函式执行的时机」的情境下适合使用:

  • 满足条件,才去执行的函式。
  • 控制函式执行的先後顺序。

假设郭靖跟欧阳克都中了毒:

let poisonA = function(){
	alert('欧阳克中毒身亡!');
};

let poisonB = function(){
 alert('郭靖中毒身亡!');
}

poisonA();
poisonB();

这样的执行顺序当然是先跳('欧阳克中毒身亡!')的视窗,再跳('郭靖中毒身亡!')。但是如果加上一个随机生成的等待时间,那视窗的弹跳顺序就不一定了。

let poisonA = function(){
	var i = Math.random()+1;
	window.setTimeout(function () {
		alert('欧阳克中毒身亡!');
	}, i * 1000)
};

let poisonB = function(){
	var i = Math.random()+1;
	window.setTimeout( () {
	alert('郭靖中毒身亡!');
	}, i * 1000 )
}

poisonA();
poisonB();

有时是('郭靖中毒身亡!')会先跳出来,有时是('欧阳克中毒身亡!')会先跳出来!

如果我们想确保('欧阳克中毒身亡!')比('郭靖中毒身亡!')早跳出来,可以这样写:

const poisonA = function(callback){
	const i = Math.random() + 1;
	window.setTimeout(function () {
		alert('欧阳克中毒身亡!');
    if (typeof callback === 'function'){
      callback();
    };
	}, i * 1000)  
};

const poisonB = function(){
	const i = Math.random() + 1;
	window.setTimeout (function() {
	alert('郭靖中毒身亡!');
	}, i* 1000 );
}

poisonA( poisonB );

这样欧阳克就会比郭靖还要早毒发身亡了!

但是如果中毒的人越来越多,一个函式呼叫另一个函式,一层一层包下去,就变成「回呼地狱」了。

再假设另外一个情境:

「王重阳参加华山论剑,只要打败黄药师、洪七公、段皇爷与欧阳锋,就会夺得『武功第一』的封号。但是不用去管王重阳与人决斗的先後顺序,只要与每个人都打过就可以。」

这时候我们可以这样做:

let fightProcess = []; //设一个空阵列,王重阳每次比武,都push到阵列中
let step = 4;  //王重阳与4个人比武

function fightA () {
  window.setTimeout(function(){
    fightProcess.push('王重阳打败黄药师');
    console.log('王重阳打败黄药师');
    if (fightProcess.length === step){   
		//比较空阵列fightProcess的长度是否与step相等,如果相等,就执行ightWinner()
      fightWinner();
    }
  },(Math.random()+1) * 1000);
};

function fightB () {
  window.setTimeout(function(){
    fightProcess.push('王重阳打败洪七公');
    console.log('王重阳打败洪七公');
    if (fightProcess.length === step){
      fightWinner();
    }
  },(Math.random()+1) * 1000);
}

function fightC () {
  window.setTimeout(function(){
    fightProcess.push('王重阳打败段皇爷');
    console.log('王重阳打败段皇爷');
    if (fightProcess.length === step){
      fightWinner();
    }
  },(Math.random()+1) * 1000);
}

function fightD () {
  window.setTimeout(function(){
    fightProcess.push('王重阳打败欧阳锋');
    console.log('王重阳打败欧阳锋');
    if (fightProcess.length === step){
      fightWinner();
    }
  },(Math.random()+1) * 1000);
}

function fightWinner(){
  console.log('王重阳天下武功第一,人称「中神通」');
  console.log(fightProcess);
}

fightA();
fightB();
fightC();
fightD();

Promise物件

Promise物件是ES6之後新增的物件,照字面的解释就是「承诺」,回传的结果只有两种:「解决」与「拒绝」。

Promise物件长成这个样子:

let myPromise = new Promise((resolve, reject) =>{
	resolve('解决');
	reject('拒绝');
})

要在一个函式中使用Promise功能,只要让它回传一个Promise物件就行了:

function urPromise(){
	return new Promise((resolve,reject) => {
		//resolve()或reject()
	});
}

Promise还提供了三种方法:

  • .then():依顺序串联执行多个promise功能。
  • Promise.all():直到全部函式都回覆resolve,或其中一个reject,才继续後面功能
  • Promise.race():只要其中一个函式resolve,不等待其他含式执行,直接行後续动作,

像刚刚那个「王重阳与四大高手比武」的过程就可以这样写:

function fightA () {
  return new Promise(function(resolve,reject){
    window.setTimeout(function() {
      console.log('王重阳打败黄药师');
      resolve('王重阳打败黄药师');
    },(Math.random()+1) * 1000);
  });
}

function fightB () {
  return new Promise((resolve,reject) =>{
    window.setTimeout(() =>{
      console.log('王重阳打败洪七公');
      resolve('王重阳打败洪七公');
    },(Math.random()+1) * 1000);
  });
}

function fightC () {
  return new Promise((resolve,reject) =>{
    window.setTimeout(() =>{
      console.log('王重阳打败段皇爷');
      resolve('王重阳打败段皇爷');
    },(Math.random()+1) * 1000);
  });
}

function fightD () {
  return new Promise((resolve,reject) =>{
    window.setTimeout(() =>{
      console.log('王重阳打败欧阳锋');
      resolve('王重阳打败欧阳锋');
    },(Math.random()+1) * 1000);
  });
}

function fightWinner(){
	console.log('王重阳天下武功第一,人称「中神通」');  
}

//加上.then可以做到依次执行
fightA()
  .then(fightB)
  .then(fightC)
  .then(fightD)
	.then(fightWinner);

我们在呼叫fightA()之後,用.then串接後面要执行的函式,这样我们就可以做到依顺序执行了。

来看看promise.all的情况:

function fightA () {
  return new Promise(function(resolve,reject){
    window.setTimeout(function() {
      console.log('王重阳打败黄药师');
      resolve('王重阳打败黄药师');
    },(Math.random()+1) * 1000);
  });
}

function fightB () {
  return new Promise((resolve,reject) =>{
    window.setTimeout(() =>{
      console.log('王重阳打败洪七公');
      resolve('王重阳打败洪七公');
    },(Math.random()+1) * 1000);
  });
}

function fightC () {
  return new Promise((resolve,reject) =>{
    window.setTimeout(() =>{
      console.log('王重阳打败段皇爷');
      resolve('王重阳打败段皇爷');
    },(Math.random()+1) * 1000);
  });
}

function fightD () {
  return new Promise((resolve,reject) =>{
    window.setTimeout(() =>{
      console.log('王重阳打败欧阳锋');
      resolve('王重阳打败欧阳锋');
    },(Math.random()+1) * 1000);
  });
}

function fightWinner(){
	console.log('王重阳天下武功第一,人称「中神通」');  
}
//不管fightA(),fightB(),fightC(),fightD()的执行顺序,只要都执行了就继续後面的程序
Promise.all([fightA(),fightB(),fightC(),fightD()])
  .then(fightWinner);

Promise.all()则会等待全部的Promise函式都执行了,才会进行後面的.then函式。

然後是promise.race:

function fightA () {
  return new Promise(function(resolve,reject){
    window.setTimeout(function() {
      console.log('王重阳打败黄药师');
      resolve('王重阳打败黄药师');
    },(Math.random()+1) * 1000);
  });
}

function fightB () {
  return new Promise((resolve,reject) =>{
    window.setTimeout(() =>{
      console.log('王重阳打败洪七公');
      resolve('王重阳打败洪七公');
    },(Math.random()+1) * 1000);
  });
}

function fightC () {
  return new Promise((resolve,reject) =>{
    window.setTimeout(() =>{
      console.log('王重阳打败段皇爷');
      resolve('王重阳打败段皇爷');
    },(Math.random()+1) * 1000);
  });
}

function fightD () {
  return new Promise((resolve,reject) =>{
    window.setTimeout(() =>{
      console.log('王重阳打败欧阳锋');
      resolve('王重阳打败欧阳锋');
    },(Math.random()+1) * 1000);
  });
}

function fightWinner(){
	console.log('王重阳天下武功第一,人称「中神通」');  
}
//只要fightA(),fightB(),fightC(),fightD()其中之一执行,就继续执行後面的程序
//但是fightA(),fightB(),fightC(),fightD()都会执行,不会取消
Promise.race([fightA(),fightB(),fightC(),fightD()])
  .then(fightWinner);

Promise.race就如同「竞赛」一样,只要有其中一个Promise函式先做到,不待其它的Promise函式完成,就直接进行.then後面的程序。但是其他的Promise函式还是会继续执行,不会取消。


<<:  事件检视器的使用介绍(二)--事件分类与筛选

>>:  IIFE立即执行函式

[DAY20]新手学Istio

I s t i o 什麽是Istio Istio是Google、IBM 和 Lyft一起开发的开源专...

Day-10 近水楼台先得月

近水楼台先得月 tags: IT铁人 区域性原则 有在组装电脑的人就会知道,电脑的储存装置包括记忆体...

Day 08 - UPDATE 把资料改成想要的样子!

上一篇我们学会了新增资料,但是如果资料打错了想要改的话应该怎们办呢?那这时候我们就需要用到我们的UP...

JavaScript Day31 - 系列目录

目录 JavaScript Day01 - 说明 说明与工具 JavaScript Day02 - ...