# 回调函数

# 定义

数组作为头等对象,是可以作为值来传递的,我们把函数作为值传递的函数叫做回调函数。

# 回调函数用来解决什么

这儿涉及到同步异步的概念(看同步异步章节).

主要就是因为异步的问题,js 引擎已经执行完毕,但是数据还没有饭回来,所以我们不得不利用各种技术去管理那段 js 引擎不执行的时间。以便我们最终能够拿到数据。

# 同步回调vs异步回调

异步函数也可以分为同步函数和异步函数,同步就是会阻塞引擎的指向,异步则不会。

  1. 同步回调
function foo(fn) {
    // ... 执行代码
    fn();
}
foo(function () {console.log(2)});
console.log(1);
var arr = [1, 2, 3];
arr.forEach(item => {console.log(item)});

异步回调

setTimeout(function () {
    console.log(1);
}, 1000);

# 回调地狱??

假如我们有这么一个业务,需要先获取用户的身份证号,然后根据身份证去查用户的成绩,然后根据成绩进行排名。

getsfz(function (sfz) {
    getcj(sfz, function (cj) {
        getmc(cj, function (paiming) {
            console.log('排名', paiming);
        });
    })
})

这样的代码常被称为“回调地狱", 也被称为“末日金字塔.但是真的就是因为它的丑陋还被人所称为地狱吗?

那么我们也可以这么写。

function getA() {
    getsfz(getB)
}

function getB(sfz) {
    getcj(sfz, getC);
}

function getC(cj) {
    getmc(cj, getD);
}
function getD(paiming) {
    console.log('排名', paiming);
}
getA();

这样书写好看多了吧,难道没有形成金字塔状,它就不是地狱了吗?

我么来看看是它如何完成的,我们通调用 A,使得B硬编码与A中,又调用C,使得C硬编码在B中,让D硬编码在C中。

硬编码使得代码变的很脆弱,它不会考虑过程中存在错误,也不考虑是否跳过错误处理流程上。这才是回调地狱的真正原因。

# 信任问题

一开始说过,引擎执行完之后就会停止,因为事件循环的一个编排,使得程序又得到了延续。同步的代码执行的控制权是在我们的手中的,我们可以进行错误的控制,但是回调函数的控制权将会发生倒转。

有一个别人写好的函数,比如我们在做支付,我们需要一个功能在用户支付之后,我们能够通过一个函数来追踪到付款的流向。我们现在借助一个第三方的函数帮助我们完成这个需求。(我们是不知道第三方函数的具体逻辑是怎么样的,这也不是我们应该关心的,我们只需要知道它可以就行)。
我们希望追踪到之后,然后删除这个订单。

function pay() {
    // 检测到用户付款了。
    // 我们借助函数来追踪一下
    zhuizong(function (flag) {
        // 假设这个第三方函数是一个异步的(一般都是异步的)。
        // 我们需要一个回调,来延迟代码的执行。
        
        // 不管有没有追踪到都会给我门返回一个标识。
        if (flag) {
            // 追踪到之后, 删除订单。
            deleteOrder();
        } else {
            // 没有追踪到 (我们可能希望重新追踪一下)
            zhuizong();
        }
        
    });
}

写完之后,确实可以,但是现在这个控制权已经到了第三方函数上了,关键看人家怎么处理,假如它要是没有进行一些判断。那么我们可能就会被咬到。 
1. 调用太早。2. 调用太晚。3.或者不调用。

# 拯救回调

我们上面讲,硬编码的不好之一就是它不能够对错误进行处理。 下面这种分离的回调,提供了一个错误一个成功的通知。 (promise 做法)

function success() {};
function fail() {};
ajax(success, fail);

错误优先. Node做法。

function response(err, data) {
    if (err) {
        throw err;
    }
    console.log(data);
}
ajax(response);

它只是会解决对错误的一个处理。并没有解决信任问题。假如你信息它,那你还需要对可能同时得到成功和失败的结果,或者什么都得不到的处理。

# 封装一个超时取消事件的工具


function timeout(func, delay) {
    var init = setTimeout(function () {
        init = null;
        fn(new Error('err'));
    }, delay);
    return function () {
        if (init) {
            setTimeout(init);
            fn.apply(this, [null].concat([].slice.call(arguments)));
        }
    }
}

function response(err, data) {
    if (err) {
        throw err;
    }
    console.log(data);
}
ajax('http://xxx.xxx.com', timeout(response, 1000));