JavaScript异步编程之回调函数
基本概念
当一个函数满足以下两点时,就可以称之为回调函数(callback )。
1、这个函数作为参数传递给另外一个函数。
2、这个函数会在未来某个时间点(条件得到满足时或执行流到达时)被调用。
例子:
(function(fn) {
let n = Math.floor(Math.random() * 10 + 1);
let _fn = fn();
if (n < 5) {
_fn.done();
} else {
_fn.fail();
}
})(callback);
function callback() {
return {
done() {
console.log("小于5");
},
fail() {
console.log("大于5");
}
}
}
回调函数的异步模式
回调函数本身并没有同步和异步之分,同步或异步主要取决于发布回调函数的任务源是什么。例如事件、Ajax(XMLHttpRequest)、setTimeout / setInterval 和 Promise 本身就是异步任务源,因此回调函数也被异步调用。如果回调函数是放在一段程序的最后去执行,那是利用了JavaScript
本身的单线程缘故,属于同步任务。
例子:
同步执行回调函数
console.log(1);
(function(callback) {
console.log(2);
callback();
console.log(3);
})(fn);
console.log(4);
function fn() {
let arr = Array(1000000);
arr.fill(1);
console.log(arr.reduce((a, b) => a + b));
}
//输出: 1 2 1000000 3 4
异步执行回调函数
console.log(1);
(function(callback) {
console.log(2);
setTimeout(() => {
callback();
}, 0);
console.log(3);
})(fn);
console.log(4);
function fn() {
let arr = Array(1000000);
arr.fill(1);
console.log(arr.reduce((a, b) => a + b));
}
//输出: 1 2 3 4 1000000
回调函数的意义
回调函数在形式上显然属于高阶函数的范畴,高阶函数的特点之一就是分解单个函数的复杂度,增强代码的可复用性。为了达到这个目的,我们往往需要抽离出那些容易变化的业务逻辑,把这部分业务逻辑放在回调函数中,这样一来可以分离业务代码中变化与不变的部分,增强了原函数的可复用性。
例如,我们想在页面上创建一组有序(顺序或倒叙)的数字按钮,其中按钮是使用什么规则去排序的,属于可变的部分,而创建按钮则是不变的部分,我们可以把可变的部分封装在函数参数中。
例子:
//不变的部分
function each(ary, callback) {
let frag = document.createDocumentFragment();
callback(ary).forEach(v => {
let node = document.createElement("button");
node.innerHTML = v;
frag.appendChild(node);
});
document.body.appendChild(frag);
}
//变化的部分:从小到大排列
function sortEach(data) {
let len = data.length;
while (--len) {
for (let i = 0; i < len; i++) {
if (data[i] > data[i + 1]) {
[data[i], data[i + 1]] = [data[i + 1], data[i]];
}
}
}
return data;
}
//变化的部分:从大到小排列
function reverseEach(data) {
let len = data.length;
while (--len) {
for (let i = 0; i < len; i++) {
if (data[i] < data[i + 1]) {
[data[i], data[i + 1]] = [data[i + 1], data[i]];
}
}
}
return data;
}
//each([2, 3, 1, 5, 4], sortEach);
each([2, 3, 1, 5, 4], reverseEach);
//输出: 5 4 3 2 1
回调地狱
首先,考虑下面这个经典的“回调地狱”代码:
addEventListener("click", function handler() {
setTimeout(function request() {
ajax("http://some.url", function response(data) {
if (data == "hello") {
handler();
} else if (data == "world") {
request();
}
});
}, 500);
});
一眼看去,我们似乎可以很快梳理出这样的异步执行顺序:等待 click 事件,然后等待定时器启动,然后等待 Ajax 响应返回。但是,这样的执行顺序,只有在最理想的状态下才会发生,并没有考虑可能导致执行顺序偏离的异常情况,例如,“定时器启动”这一步骤执行失败,那么就永远不会到达“ Ajax 响应”这一步,我们不得不转到“定时器启动”失败时的错误处理流程中。这只是我们列举的一种情况,而实际开发中,我们需要为每一个异步事件指定所有的可能性和路径,那么代码就会变得非常复杂,追踪异步流的难度会成倍增加。这就是回调地狱。
从根本上讲,回调地狱的出现,是因为我们丧失了自己程序一部分的控制权,这种“控制转移”导致我们不得不“核实”任何可能出现的情况。
如有错误,欢迎指正,本人不胜感激。
共同学习,写下你的评论
评论加载中...
作者其他优质文章