回调函数
- 函数也是一种数据类型,可以做参数也可以做返回值,一般情况下,把函数作为参数的目的就是为了获取函数内部的异步操作结果
异步操作
- 注意:凡是需要得到一个函数的内部异步操作的结果
- 异步操作类型例如:
- setTimeout
- readFile
- writeFile
- ajax
- readdir
- 这种情况就必须通过回调函数,往往异步的API都伴随有一个回调函数
console.log(1)
setTimeout((function){
console.log(2)
console.log('hello')
},0)
console.log(3)
//即使计时器的时间为0或者没有,程序也是按照1,3,2,hello的顺序执行,执行的结果也是1,3,2,hello,不会去等待异步函数setTimeout执行后去执行下一步函数,而是先跳过
函数封装异步操作
function add(x,y){
console.log(1)
setTimeout(function(){
console.log(2)
var ret = x + y
return ret
},1000)
console.log(3)
//函数到这里就会结束了,不会等待前面的异步函数定时器setTimeout执行后再去执行下一步函数,所以返回了默认值,执行的顺序就是1,3,执行的结果是1,3,undefined,不会去执行封装里面的函数了,等于直接跳过了,所以最后的才会是未定义undefined
}
console.log(add(10,20)) //undefined
不成立的结果
function add(x,y){
console.log(1)
setTimeout(function(){
console.log(2)
var ret = x + y
return ret
},1000)
console.log(3)
return ret //这里也不会执行封装函数里面的程序的,最后也会是undefined
}
console.log(add(10,20)) //undefined
可以强制执行封装函数的结果,但是没有意义
var ret
function add(x,y){
console.log(1)
setTimeout(function(){
console.log(2)
var ret = x + y
return ret
},1000)
}
add(10,20)
setTimeout(function(){
console.log(ret)
},1000)
//通过定义全局变量,再通过两个定时器setTimeout先执行前面定时器的原则,所以得到了封装函数里面定时器的结果,执行结果就是1,2,30
回调函数
function add(x,y,callback){
//callback 就是回调函数,callback会触发下面add回调函数的执行,从能够调动里面封装函数计时器的执行,从而得到封装函数的结果
//var x = 10
//var y = 20
//var callback = function (ret){console.log(ret)}
setTimeout(function(){
var ret = x + y
callback(ret) //这里的ret是实参,这里会触发回调函数的执行,然后把实ret的结果传递给下面形参ret
},1000)
}
//这里面的ret就是形参
add(10,20,function(ret){
console.log(ret) //最后在这里得到最后的结果
})
callback hell
- 这种回调函数是一种嵌套形式的
//获取第一份数据
$.get(url,(data1)=>{
console.log(data1)
//获取第二份函数
$.get(url,(data2)=>{
console.log(data2)
//获取第三份数据
$.get(url,(data3)=>{
console.log(data3)
//h还可以获取更多的数据
})
})
})
promise
- 这种回调函数是一种油管形式的(异步调用链式编程),就像是一节一节的相连接的,永远是一层的
function getData(url){
return new Promise((resolve,reject) =>{
$.ajax({
url,
success(data){
resolve(data)
},
error(err){
reject(err)
}
})
})
}
const url1 = '/data1.json'
const url2 = '/data2.json'
const url3 = '/data3.json'
getData(url1).then(data1 =>{
console.log(data1)
return getData(url2)
}).then(data2 =>{
console.log(data2)
return getData(url3)
}).then(data3 =>{
console.log(data3)
}).catch(err => console.error(err))
promise例子
const url ='https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png'
const url1 ='https://www.baidu.com/img/flexible/logo/pc/result.png'
function loadImg(src){
return new Promise((resolve,reject) =>{
const img =document.createElement('img')
img.onload = () =>{
resolve(img)
}
img.onerror = () =>{
const err = new Error(`图片加载失败 ${src}`)
reject(err)
}
img.src = src
})
}
loadImg(url).then(img =>{
console.log(img.width)
return img //普通对象
}).then(img =>{
console.log(img.height)
return loadImg(url1) //promise实例对象
}).then(img1 => {
console.log(img1.height)
}).catch(ex => console.error(ex))
- 这样的异步是无法保证文件的加载顺序
var fs requires('fs')
fs.readFile('./data/a.txt',function(err,data){
if(err){
//return.console.log('读取失败')
//抛出异常
// 1.阻止程序的执行
// 2.把错误消息打印到控制台
throw err
}
console.log(data)
})
fs.readFile('./data/b.txt',function(err,data){
if(err){
throw err
}
console.log(data)
})
fs.readFile('./data/c.txt',function(err,data){
if(err){
throw err
}
console.log(data)
})
//上面三个文件读取的异步代码,通常来说文件大的读取速度较慢,但是文件大的不一定慢,所以运行顺序不一定是从头到尾的顺序,取决操作系统的调度顺序,但是我们无法确定其中的调度顺序
- 所以有了把回调函数嵌套的方式来保证顺序的方式
var fs requires('fs')
fs.readFile('./data/a.txt',function(err,data){
if(err){
//return.console.log('读取失败')
//抛出异常
// 1.阻止程序的执行
// 2.把错误消息打印到控制台
throw err
}
console.log(data)
fs.readFile('./data/b.txt',function(err,data){
if(err){
throw err
}
console.log(data)
fs.readFile('./data/c.txt',function(err,data){
if(err){
throw err
}
console.log(data)
})
})
})
-
但是回调嵌套的方式,非常杂乱不美观,不利于维护,所以在EcmaScript 6中新增了一个API:promise
1593424330520.png - EcmaScript 6中本身就创建了一个promise类,而我们在这里只不过是创建(new)了一个实例,用实例调用promise中的方法而已
- resolve(解决),
reject(驳回)本身只是个形参,用来接收err和data的状态 - promise本身不是异步函数,里面的readFile是异步函数
-
.then的意思就是调用new出来的promise函数里面的方法和this调用自己所处这个函数意思差不多,就是相当一个代词,代表它所代表的函数
var fs = require ('fs')
var read =new Promise(function(resolve,reject){
fs.readFile('./data/a.txt','utf-8',function(err,data){
if(err){
//如果promise中的任务失败了,pending的状态会变成Rejected
//调用reject就相当于调用了then方正的第二个参数函数
reject(err)
}else{
//如果promise中的任务成功了,pending的状态就会变成Resolved
//这里调用resolve方法实际上就是then方法传递的那个function
resolve(data)
}
})
})
var read1 =new Promise(function(resolve,reject){
fs.readFile('./data/b.txt','utf-8',function(err,data){
if(err){
reject(err)
}else{
resolve(data)
}
})
})
var read2 =new Promise(function(resolve,reject){
fs.readFile('./data/c.txt','utf-8',function(err,data){
if(err){
//如果promise中的任务失败了,pending的状态会变成Rejected
//调用reject就相当于调用了then方正的第二个参数函数
reject(err)
}else{
//如果promise中的任务成功了,pending的状态就会变成Resolved
//这里调用resolve方法实际上就是then方法传递的那个function
resolve(data)
}
})
})
//then接收的function(data)就是容器中的resolve函数,经过上面判断文件读取是否成功,还接收了传过来的文件数据 //then接收的function(err)就是容器中的reject函数,经过上面判断文件读取是否成功,如果不成功就会回来调用'文件读取失败'的函数信息
read
.then(function(data){
console.log(data)
//当read读取成功的时候
//当前函数中return的结果就可以在后面的then中function(data)接收到
//当你return 123,后面的.then接收的就是123,也就是说前面的return返回啥,后面收到的就是啥,没有return就是undefined
//当然return如果返回的是promise早已赋值好的read对象,后面.then接收的也是个对象
//当return一个read也就是promise对象的时候,后面的.then中的,方法的第一个参数将会作为read的resolve
return read1
},function(err){
console.log('文件读取失败',err)
})
.then(function(data){
console.log(data)
return read2
},function(err){
console.log('文件读取失败',err)
})
.then(function(data){
console.log(data)
console.log('end')
},function(err){
console.log('文件读取失败',err)
})
异步调用链式函数封装
var fs = require ('fs')
function pReadFile(filePath){
return new Promise(function(resolve,reject){
fs.readFile(filePath,'utf-8',function(err,data){
if(err){
reject(err)
}else{
resolve(data)
}
})
})
}
pReadFile('./data/a.txt')
.then(function(data){
console.log(data)
return pReadFile('./data/b.txt')
},function(err){
console.log('文件读取失败',err)
})
.then(function(data){
console.log(data)
return pReadFile('./data/c.txt')
},function(err){
console.log('文件读取失败',err)
})
.then(function(data){
console.log(data)
console.log('end')
},function(err){
console.log('文件读取失败',err)
})
//reject是个可选参数,也可以写,还可以简写
const file1 = './data/a.txt'
const file2 = './data/b.txt'
const file3 = './data/c.txt'
pReadFile(file1).then(data =>{
console.log(file1)
return pReadFile(data)
throw err
}).then(data =>{
console.log(data)
return pReadFile(file3)
throw err
}).then(data => {
console.log(data)
throw err
})
pReadFile(file1).then(data =>{
console.log(file1)
return pReadFile(data)
}).then(data =>{
console.log(data)
return pReadFile(file3)
}).then(data => {
console.log(data)
}).catch(err => console.error(err)) //这里会抓取前面的错误
async函数把异步操作变成同步
async函数可以加在任何函数前面,,被加在async函数的函数会类似异步函数的操作,而异步函数会变成同步函数(这种现象就是,在加载的时候,不会像同步函数一样严格的按照代码顺序执行,被加async函数的函数有像异步函数一样有时候快有时候慢,被加async函数的异步函数同时会想同步一样执行,主要是看加载速度,速度可以的话,会按照代码顺序执行)
//Promise 还是不太方便,还是需要回调函数
//async函数的本质就是生成器函数的语法糖,所以对于开发人员来讲,已经不需要关心生成器函数,原先Genarator函数需要next一步一步的调用,后来有人搞了第三方库co(生成器函数),最后官方发布了官方生成器函数async函数
// * 换成了 async,yield换成了await,这样的话更语义了
//node LTS版本都支持async函数,babel也支持编译转换async函数
//在新的版本中Ecmascript 中增加了一个函数:async函数
//语法
//async function add(x,y) {
//在async函数内部可以使用await的方式来操作异步API
//return x + y
//let a = await 1
//let b = await 2
//let c = await 'hello'
//await是一种新的语法,只能在async函数中使用
//await一般是在其后面跟一个 promise 操作API
//}
//let ret = add(10,20)
//console.log(ret)
//callback的方式
const fs = require('fs')
/*fs.readFile('./a.txt','utf-8',function (err,data) {
if (err){
throw err
}
console.log(data)
})
fs.readFile('./b.txt','utf-8',function (err,data) {
if (err){
throw err
}
console.log(data)
})*/
//Promise的方式
/*
function readFile(...args) {
return new Promise((resolve,reject) =>{
//出现在方法定义参数中叫做:rest参数,出现在方法调用的参数中叫做:数组的展开操作符
//...args 数组的展开操作符.
// fs.readFile.apply(null,args)
fs.readFile(...args,(err,data) =>{
if (err){
return reject(err)
}
resolve(data)
})
})
}
readFile('./a.txt','UTF-8').then(data =>{
console.log(data)
return readFile('./b.txt','UTF-8')
})
.then(data =>{
console.log(data)
return readFile('./c.txt','UTF-8')
})*/
//async方式,任何函数前面加上了async就变为了一个async函数(类似于异步函数,真正的异步函数只要异步操作才是异步函数)
//只有在async 函数中才可以使用await结合Promise来使用同步的风格来调用异步API,让异步执行像同步一样执行,但本质上是同步的
//async函数的作用就是让你的异步代码写起来像同步代码一样
let util = require('util')
let readFile = util.promisify(fs.readFile)
readFile('./a.txt','utf-8').then(data =>{
console.log(data)
})
readFile('./b.txt','utf-8').then(data =>{
console.log(data)
})
readFile('./c.txt','utf-8').then(data =>{
console.log(data)
})
//函数中的await就像是叫其他函数执行等它一下的样子,这样的函数写出来和同步一样,异步加载不同加载的时候,速度不一样!
async function read() {
console.log(1)
//async函数中如果遇到await异步操作,则方法会等待,而我们的程序不会等待
const dataA = await readFile('./a.txt','utf-8')
const dataB = await readFile('./b.txt','utf-8')
const dataC = await readFile('./c.txt','utf-8')
//这里面的三个函数是串行的
console.log(dataA,dataB,dataC)
console.log(2)
//async函数返回的是一个Promise对象,你需要通过.then的方式来接收async函数中的返回值
return [dataA,dataB,dataC]
}
console.log(3)
read()
read().then(data =>{
console.log(data)
})
console.log(4)
//在async函数中调用另一个async函数
async function fn() {
const data = await read()
console.log(data)
}
fn()
