热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Web前端学习笔记——NodeJS之回调函数、Promise对象、async函数

目录回调函数回调函数概念JavaScript回调函数示例:封装原生的 ajax 操作示例:实现拷贝方法示例:读取文件中的todos列表数据示例:把任务持久化保存到文件中异常处理tr

目录

回调函数

回调函数概念

Javascript 回调函数

示例:封装原生的 ajax 操作

示例:实现拷贝方法

示例:读取文件中的 todos 列表数据

示例:把任务持久化保存到文件中

异常处理

try-catch 处理异常

Callback 处理异常

Promise 对象

回调地狱

Promise 概念

Promise 基本用法

几个例子

错误处理

Promise.all()

Promise.race()

async 函数

回调函数

回调函数概念

  • 知乎 – 回调函数(callback)是什么?

你寻求一个陌生人的帮助。

  • 等待他帮你完成这件事儿
  • 回去继续你的工作,留一个电话给他(注册回调)
    • 他帮我完整这件事儿之后我干嘛?
    • 我决定,他只需要把结果告诉我
    • 结果:有的结果由数据,有的结果无数据
  • 当他完成打你的电话通知你(调用回调函数)

你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。回答完毕。 作者:常溪玲链接:https://www.zhihu.com/question/19801131/answer/13005983来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

我勒个去,一句话搞定的事,非得啰嗦那么多?

简单举例来说就是,我打电话找你帮忙办事,但是不确定什么时间办完,我让你办完了再电话通知我。我让你通知我就是我设定的回调函数!一般用于异步通信场景。如果我不挂电话,非等你办完了知道结果了再挂这就不属于异步通信,也无需回调!作者:柳明军链接:https://www.zhihu.com/question/19801131/answer/43799125来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Javascript 回调函数

获取普通方法的返回值(方式一 return):

function add (x, y) {
return x + y
}
const ret = fn() // => 123

获取普通方法的返回值(方式二 函数):

// 是不是傻?同步回调,闲的蛋疼!
function add (x, y, callback) {
callback(x + y)
}
add(10, 20, function (ret) {
console.log(ret)
})

对于上面的代码我们肯定会有疑问,是不是傻?干嘛搞这么麻烦,第一种明显就 OK 了,难道第二种只是一种方式问题吗,聪明人肯定会选择第一种。

那大家姑且就先把它当作一种方式吧,在这里是先让大家明白一个道理:在 Javascript 中函数也是一种数据类型,函数也可以当作参数进行传递 。

那我们到底什么时候需要使用回调函数呢?

请记住: 当需要得到一个函数中的异步操作结果的时候,我们就必须使用回调函数了(上面的第二种方式)。

  • 定时器
  • ajax
  • readFile、writeFile

请看下面的例子:

例如,获取一个函数中异步操作的结果:

function fn () {
setTimeout(function () {
// 我想调用 fn 得到这里的 num
const num = 123
}, 1000)
}

想法一(行不通):

function fn () {
console.log(1)
setTimeout(function () {
console.log(2)
// 我想调用 fn 得到这里的 num
const num = 123
// 从返回值角度来讲,这里的 return 也只是返回给了当前的函数,而非外部的 fn 函数
return num
}, 1000)
// 到这里 fn 函数就执行结束了,所以不可能得到里面 return 的结果
console.log(3)
}

想法二(行不通):

function fn () {
console.log(1, '函数开始执行')
let num
// 定时器是异步的,所以遇到定时器不会等待,函数会继续往后执行
setTimeout(function () {
console.log(2)
// 我想调用 fn 得到这里的 num
num = 123
}, 1000)
// 到这里 fn 函数就执行结束了(定时器还没有被调用),所以你拿到的 num 就是 undefined
console.log(3)
}

正确的方式(通过函数来接收异步操作结果,这就是回调函数,因为不是立即调用,而是回头再调用):

// 2. 在 fn 函数中通过形参 callback 接收了 handler 函数
function fn (callback) {
// var callback = handler
// callback 就是我们的 handler
console.log(1, '函数开始执行')
// 定时器是异步的,所以遇到定时器不会等待,函数会继续往后执行
setTimeout(function () {
console.log(2)
// 我想调用 fn 得到这里的 num
const num = 123
// 定时器操作结束,我们就可以在这里调用 callback(也就是我们的 handler)函数,把结果 num 传递给了该函数
// 我们这里调用 callback 也就是在调用 handler
callback(num)
}, 1000)
// 到这里 fn 函数就执行结束了(定时器还没有被调用),所以你拿到的 num 就是 undefined
console.log(3)
}
function handler = function (data) {
console.log('handler 函数被调用了:', data)
}
// 1. 这里把 handler 传递到了 fn 函数中
fn(handler)

上面的方式比较繁琐,我们没必要单独定义一个全局函数,我们可以可以在调用的时候直接传递一个匿名函数即可:

function fn (callback) {
setTimeout(function () {
const num = 123
callback(num)
})
}
fn(function (data) {
console.log('回调函数被执行了:', data)
})

示例:封装原生的 ajax 操作

MDN – Using XMLHttpRequest

function reqListener () {
console.log(this.responseText);
}
var oReq = new XMLHttpRequest();
oReq.Onload= reqListener;
oReq.open("get", "yourFile.txt", true);
oReq.send();

示例:实现拷贝方法

已知 fs.readFile 可以读取文件,fs.writeFile 可以写文件。请帮我封装一个方法:copy。要求调用方式如下:

copy('被复制文件', '复制到的目标文件', function (err) {
// err 成功是 null 错误是一个 错误对象
})

示例:读取文件中的 todos 列表数据

已知一个 json 文件内容如下:

{
"todos": ["吃饭", "睡觉", "打豆豆"]
}

请帮我写一个方法,调用该方法得到的结果就是 todos 数组 。

示例:把任务持久化保存到文件中

已知有一个 json 文件内容如下:

{
"todos": ["吃饭", "睡觉", "打豆豆"]
}

请帮我写一个方法,调用该方法可以帮我把指定的数据存储到 json 文件中的 todos 中。例如:

// 该方法肯定是异步的,所以无论操作成功与否你都必须告诉我
// err 是错误的标志,如果有错你就告诉我,如果没错就给我一个 null
// 那调用者就可以通过 err 参数来判定 addTodo 的操作结果到底成功与否
addTodo('写代码', function (err) {
})

异常处理

  • try-catch
  • 回调函数 Error First
  • 如果封装的函数中有错误,不要在函数中自行处理,一般是把错误对象放到回调函数的第一个参数,这是一种约定规则,错误优先,由调用者决定如何处理这个错误
  • 在自己封装的回调函数中不要自己处理错误
  • 如果有错,则把错误对象作为回调函数的第一个参数传递给回调函数
  • 错误优先:Error First

try-catch 处理异常

Callback 处理异常

Promise 对象

回调地狱

《Web前端学习笔记——NodeJS之回调函数、Promise对象、async 函数》

const fs = require('fs')
fs.readFile('./data/a.txt', 'utf8', (err, dataA) => {
if (err) {
throw err
}
fs.readFile('./data/b.txt', 'utf8', (err, dataB) => {
if (err) {
throw err
}
fs.readFile('./data/c.txt', 'utf8', (err, dataC) => {
if (err) {
throw err
}
fs.writeFile('./data/d.txt', dataA + dataB + dataC, err => {
if (err) {
throw err
}
console.log('success')
})
})
})
})

Promise 概念

  • 一个容器,用来封装一个异步任务
  • 三种状态
    • Pending
    • Resolved
    • Rejected
  • 成功调用 resolve
  • 失败调用 reject

Promise 基本用法

几个例子

实例一:Promise 版本的定时器

function sleep(time) {
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve()
}, time)
})
}
sleep(1000)
.then(() => {
console.log('吃饭')
return sleep(2000)
})
.then(() => {
console.log('睡觉')
return sleep(3000)
})
.then(() => {
console.log('坐火车回家')
})

封装 Promise 版本的 readFile :

function readFile(...args) {
return new Promise((resolve, reject) => {
fs.readFile(...args, (err, data) => {
err ? reject(err) : resolve(data)
})
})
}
readFile('./data/a.txt', 'utf8')
.then(data => {
console.log(data)
})

另一个例子:读取文件

function readFile(...args) {
return new Promise((resolve, reject) => {
fs.readFile(...args, (err, data) => {
err ? reject(err) : resolve(data)
})
})
}
function writeFile(...args) {
return new Promise((resolve, reject) => {
fs.writeFile(...args, err => {
err ? reject(err) : resolve()
})
})
}
let ret = ''
readFile('./data/a.txt', 'utf8')
.then(data => {
ret += data
return readFile('./data/b.txt', 'utf8')
})
.then(data => {
ret += data
return readFile('./data/c.txt', 'utf8')
})
.then(data => {
ret += data
// fs.writeFile('./data/e.txt', ret, err => {
// })
return writeFile('./data/e.txt', ret)
})
.then(() => {
console.log('success')
})

示例:封装 Promise 版本的 ajax

var ajax = {}
ajax.get = function (url) {
return new Promise((resolve, reject) => {
var oReq = new XMLHttpRequest();
oReq.Onload= function () {
resolve(this.responseText)
}
oReq.open("get", url, true);
oReq.send()
})
}

带有业务的封装:

var ajax = {}
ajax.get = function (url) {
return new Promise((resolve, reject) => {
var oReq = new XMLHttpRequest();
oReq.Onload= function () {
// callback(this.responseText)
resolve(this.responseText)
}
oReq.open("get", url, true);
oReq.send()
})
}
function duquabc() {
return new Promise((resolve, reject) => {
let ret = ''
ajax.get('./data/a.txt')
.then(data => {
ret += data
return ajax.get('./data/b.txt')
})
.then(data => {
ret += data
return ajax.get('./data/c.txt')
})
.then(data => {
ret += data
resolve(ret)
})
})
}
duquabc().then(ret => {
console.log(ret)
})

错误处理

  • then 方法的第二个参数
    • 仅捕获 Promise 本身的异常
  • catch 方法(推荐)
    • 不仅可以捕获 Promise 的异常
    • 还是可以捕获 resolve 函数中的异常
    • 如果后面还有 then 无法阻止
  • then 方法无法被阻止

Promise.all()

Promise.race()

async 函数

异步串行:

const fs = require('fs')
const util = require('util')
// Node 通过核心模块 util 提供了一个非常便利的方法:promisify
const readFile = util.promisify(fs.readFile)
const writeFile = util.promisify(fs.writeFile)
// async function main() {
// // 异步串行
// // 小明
// // 打酱油
// // 买鱼
// const dataA = await readFile('./data/a.txt', 'utf8')
// const dataB = await readFile('./data/b.txt', 'utf8')
// console.log(dataA, dataB)
// }
// main()

异步并行:

// async function main() {
// // 异步并行
// // 不要加 await 关键字
// // 得到的结果是 Promise 对象
// const aTask = readFile('./data/a.txt', 'utf8')
// const bTack = readFile('./data/b.txt', 'utf8')
// const dataA = await aTask
// const dataB = await bTack
// console.log(dataA, dataB)
// }
// main()

推荐阅读
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 深入解析Linux下的I/O多路转接epoll技术
    本文深入解析了Linux下的I/O多路转接epoll技术,介绍了select和poll函数的问题,以及epoll函数的设计和优点。同时讲解了epoll函数的使用方法,包括epoll_create和epoll_ctl两个系统调用。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • 突破MIUI14限制,自定义胶囊图标、大图标样式,支持任意APP
    本文介绍了如何突破MIUI14的限制,实现自定义胶囊图标和大图标样式,并支持任意APP。需要一定的动手能力和主题设计师账号权限或者会主题pojie。详细步骤包括应用包名获取、素材制作和封包获取等。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 近期,某用户在重启RAC一个节点的数据库实例时,发现启动速度非常慢。同时业务部门反馈连接RAC存活节点的业务也受影响。通过对日志的分析, ... [详细]
author-avatar
阳光无限好1981
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有