回调函数

小明在奶茶店点了奶茶,店员开始制作奶茶,此时“制作奶茶”与“小明等待奶茶”是一个同时进行的不同的两个事件(任务),那么,小明获取店员制作成功的奶茶是从“制作奶茶”这一事件获取的结果,所以小明才能够完成“购买奶茶”这一事件。如果,小明在“购买奶茶”这一事件中,不想一直等待而是想去做一些其他的事情,比如购买冰淇淋。

现在,我们将这一案例抽取为一个个事件,用 JavaScript 函数体现出来:

// 小明购买奶茶事件
function buyTea() {
console.log('购买奶茶...')
} // 店员制作奶茶事件
function makeTea() {
console.log('制作奶茶...')
} // 小明的另一个事件
function buyIcecream() {
console.log('购买冰淇淋...')
}

目前,这些事件属于同步任务(事件),它们被主线程由上到下依次执行,无法形成同一时间内完成多个事件。你会发现,这些事件之间各自独立,如何将它们有机地、有序地结合在一起是一个问题。

因此,在 JavaScript 中,有一种解决方式这种问题的方式叫做异步任务(事件)。将这些独立的事件结合在一起的解决方式通常利用回调函数。在 JavaScript 中,函数作为第一等公民的存在,可以将函数作为对象传递给方法作为实参进行调用,即回调函数

请转至“[JS]函数作为值”一文,了解什么函数是如何作为值,并且可以作为函数实参进行传递以及调用的。

function buyThing(money = 0, callback) {
let isPay = false
if (money >= 5) isPay = true
callback(isPay)
} buyThing(10, function (isPay) {
if (isPay) {
console.log('购买奶茶...')
setTimeout(() => {
console.log('奶茶制作完成!!!')
buyThing(20, function (isPay) {
if (isPay) {
console.log('购买冰淇淋...')
setTimeout(() => {
console.log('冰淇淋制作完成!!!')
buyThing(30, function(isPay) {
// ...做很多事件,多次调用buyThing函数
})
}, 2000)
} else {
console.log('未支付金额...')
}
})
}, 1000)
} else {
console.log('未支付金额...')
}
})

回调函数确实将这些独立事件有机地结合在了一起,但是随之而来的就是面临着一些其他问题,即回调地狱。

现在,我们明白了回调函数的好处。再列举一个例子,深入了解回调函数的好处在哪。若实现一个简单的计算器,其功能有加、减、乘、除等运算,通常情况下会想到一个函数获得两个参数,并将运算类型作为字符串传递给函数参数以实现不同需求。

function calculate(x, y, type) {
if (type == 'minus') {
return x - y
} else if (type == 'add') {
return x + y
} ......
} let result = calculate(10, 20, 'minus') // -10

上述代码,存在一个明显的问题。如果在减法中做其他的限制条件(或增加源代码的功能),它会影响到整个 calculate 函数本身。再者,如果我扩展 calculate 函数功能,它也会影响到函数本身。对于这种情况,我们寄希望于回调函数,通过它来解决这个问题。

// calculate本体做一些基本的判断(限制)
function calculate(x, y, callback) {
if (x < 0 || y < 0) {
throw new Error(`Numbers must not be negative!`)
}
if (typeof x !== 'number' || typeof y !== 'number') {
throw new Error(`Args must be number type!`)
}
return callback(x, y, 'not problem!!!') // 向外提供更多细节
} // 没有做任何附加限制
calculate(10, 20, function (x, y, more) {
console.log(more) // 'not problem!!!'
return x * y
}) // 做了一些附加限制
calculate(5, 5, function (x, y, more) {
console.log(more) // 'not problem!!!'
if (x + y <= 10) {
throw new Error(
'The sum of the two numbers must be greater than 10'
)
}
return x * y
})

现在,调用 calculate 函数时,可以在回调函数中做一些附加限制条件,它不会影响到 calculate 这个函数本体。并且,回调函数可以从 calculate 函数本体中获取更多的细节(信息),通过这些信息我们又能做出更多的操作。

let points = [40, 100 ,10, 5, 25]

points.sort(function (a, b) => {
return a - b
}) // [5, 10, 25, 40, 100] // ...另一种比较方式...
points.sort(function (a, b) => {
if (a < b) {
return -1
}
if (a > b) {
return 1
}
return 0
}) // [5, 10, 25, 40, 100]

在回调函数中不同的排序方式可以决定最后的结果。回调函数使得程序更加灵活

回调地狱

在上面的“小明买奶茶”案例中,回调内部再嵌套回调,其代码形状上看着像180°旋转之后的金字塔,这种层层嵌套就是回调地狱。

因此,Promise 可以解决回调地狱的问题。Promise 是一个对象,用于表示一个异步操作的最终完成(或失败)及其结果值。

利用 Promise 解决“小明买奶茶”回调地狱:

function buyThing(money, timeout) {
const promise = new Promise((resolve, reject) => {
console.log('事件正在进行中...')
setTimeout(() => {
if (money >= 5) {
console.log(`支付金额:${money}`)
resolve('success to pay!')
} else {
reject('unsuccess to pay!')
}
}, timeout)
})
return promise
} buyThing(10, 1000)
.then((res) => {
console.log('奶茶制作完成!!!')
return buyThing(20, 2000)
})
.then((res) => {
console.log('冰淇淋制作完成!!!')
})

在代码层面,使用 Promise 之后,解决了多层回调函数调用导致的“金字塔”现象。让我们看看实现效果:

请转至 MDN 关于 Promise 的解释:Promise - JavaScript | MDN

[JS]回调函数和回调地狱的更多相关文章

  1. 回调函数,回调函数使用call

    回调函数:一个函数b作为参数,给另外一个函数a使用.并且在执行a之后(注意不一定是执行完a),再去执行b这个函数. 上代码: function a(callback) { alert("我是 ...

  2. JS回调函数(深入篇)

    <有些错别字> 在Javascript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用.既然函数实际上是对象:它们能被“存储”在变量中,能作为函数参数被传递,能在函数中 ...

  3. JS回调函数深入篇

    <有些错别字> 在Javascript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用.既然函数实际上是对象:它们能被“存储”在变量中,能作为函数参数被传递,能在函数中 ...

  4. 常用js,css文件统一加载方法,并在加载之后调用回调函数

    原创内容,转载请注明出处! 为了方便资源管理和提升工作效率,常用的js和css文件的加载应该放在一个统一文件里面完成,也方便后续的资源维护.所以我用js写了以下方法,存放在“sourceControl ...

  5. js回调函数以及同步与异步

    1. 背景介绍javascript的单线程特性由于javascript语言是一门“单线程”的语言,所以,javascript就像一条流水线,仅仅是一条流水线而已,要么加工,要么包装,不能同时进行多个任 ...

  6. 关于js中的回调函数callback

    来源于:http://www.jianshu.com/p/6bc353e5f7a3 前言 其实我一直很困惑关于js 中的callback,困惑的原因是,学习中这块看的资料少,但是平时又经常见,偶尔复制 ...

  7. js 回调函数 精析

      UpdateTime--2018年9月13日16点51分 1.什么是回调函数? 在JavaScript中,回调函数具体的定义为: 函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行 ...

  8. 关于 js 中的回调函数 callback

    本文写于1年前 曾经的学习文章如今拿出来分享 前言 其实我一直很困惑关于js中的callback,困惑的原因是,学习中这块看的资料少,但是平时又经常见,偶尔复制一下前人代码,功能实现了也就不再去追其原 ...

  9. JS回调函数(理解篇)

    概述: 回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数.回调函数不是由该函数的实现方直接调用,而 ...

随机推荐

  1. 微信sdk上传图片大小1k,损坏的问题以及微信上传图片需要的配置

    微信公众号的appid和appsecret有问题,会导致上传图片大小为1k这个问题 微信上传图片需要设置公众号的'JS接口安全域名'

  2. 使用 Docker 秒速搭建多版本 PHP 开发环境

    目录 目标 下载 代理设置 配置环境 PHP 7.2.x,占用本地端口 8081 PHP 5.6.x,占用本地端口 8082 端口映射 local.php72.com -> 127.0.0.1: ...

  3. 50、django工程(ajax)

    50.1.ajax介绍: 1.ajax是在不跳转当前url地址的情况偷偷的往后台发送数据做增删改数据的操作,如果成功返回结果刷新当前页面,失败则提醒, 使用 id 或 name 属性. 2.模态对话框 ...

  4. AcWing 1140. 最短网络

    农夫约翰被选为他们镇的镇长! 他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场. 约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场. 约翰的农场的编号是1,其他农场 ...

  5. Object 中的默认方法

    1.public final native Class<?> getClass() getClass方法,返回该实例的java.lang.Class类,例如 Object obj = ne ...

  6. php 扩展 rabbitmq popt

    首先是rabbitmq-c-master.tar.gz包, 可以访问https://github.com/alanxz/rabbitmq-c去下载最新的 wget https://github.com ...

  7. vim下出现^M怎么解决

    将window下的文本文件上传到linux上,在读取数据文件时,在每一行数据后会出现^M字符.   为什么会出现这种情况呢: 因为windows.linux.os系统的换行符标准不同: 先了解下概念, ...

  8. MySQL | 使用Xtrabackup进行备份和备份恢复

    备份 进行备份前需要先创建备份用户,直接使用 root 用户进行备份也行,但是这样不太规范. create user backup@'localhost' identified by '123456' ...

  9. HCNA Routing&Switching之动态路由协议OSPF基础(二)

    前文我们主要了解了OSPF的区域.区域分类.路由器类型.OSPF的核心工作流程,回顾请参考:https://www.cnblogs.com/qiuhom-1874/p/15025533.html:今天 ...

  10. 5.Java流程控制

    所有的流程控制语句都可以相互嵌套.互不影响 一.用户交互Scanner Scanner对象 之前我们学的基本语法中我们并没有实现程序和人的交互,但是Java给我们提供了这样一个工具类,我们可以获取用户 ...