回调
写了却不调用,给别人调用的函数就是回调 即回头调用一下这个函数
function f1(){}
function f2(fn){
fn()
}
f2(f1)
上述代码的 f1就是回调
因为我只把它创建出来了,我没有调用它而是f2调用了它
异步和回调的关系
- 异步任务需要用到回调函数来通知结果
- 但是回调函数不一定只用在异步任务里
- 回调可以用到同步里 比如
array.forEach(n=>console.log(n)) // 这个就是同步回调
callback hell 回调地狱
为什么会有回调地狱的出现呢? 我将以下面的例子来解释说明
没有顺序的读取 a b c 三个文件
const fs = require('fs')
fs.readFile('./a.txt', 'utf8', function (err, data) {
if (err) {
// return console.log('读取错误');
// 抛出异常
// 1.阻止程序的执行 2. 把错误消息打印到控制台
throw err
}
console.log(data);
})
fs.readFile('./b.txt', 'utf8', function (err, data) {
if (err) {
// return console.log('读取错误');
// 抛出异常
// 1.阻止程序的执行 2. 把错误消息打印到控制台
throw err
}
console.log(data);
})
fs.readFile('./c.txt', 'utf8', function (err, data) {
if (err) {
// return console.log('读取错误');
// 抛出异常
// 1.阻止程序的执行 2. 把错误消息打印到控制台
throw err
}
console.log(data);
})
这里打印的data是没有顺序的,谁快谁就先执行。
PS F:\nodejs\Node_test> node .\13.回调地狱.js
hello aaaa
hello bbbb
hello cccc
PS F:\nodejs\Node_test> node .\13.回调地狱.js
hello aaaa
hello cccc
hello bbbb
PS F:\nodejs\Node_test> node .\13.回调地狱.js
hello aaaa
hello cccc
hello bbbb
如果想要有顺序的分别 读取 a b c 三个文件
const fs = require('fs')
fs.readFile('./a.txt', 'utf8', function (err, data) {
if (err) {
// return console.log('读取错误');
// 抛出异常
// 1.阻止程序的执行 2. 把错误消息打印到控制台
throw err
}
console.log(data);
fs.readFile('./b.txt', 'utf8', function (err, data) {
if (err) {
// return console.log('读取错误');
// 抛出异常
// 1.阻止程序的执行 2. 把错误消息打印到控制台
throw err
}
console.log(data);
fs.readFile('./c.txt', 'utf8', function (err, data) {
if (err) {
// return console.log('读取错误');
// 抛出异常
// 1.阻止程序的执行 2. 把错误消息打印到控制台
throw err
}
console.log(data);
})
})
})
我只需一层一层的嵌套即可,等读取a.txt完毕后再去执行b.txt,等读取b.txt完毕后再去执行c.txt
PS F:\nodejs\Node_test> node .\13.回调地狱.js
hello aaaa
hello bbbb
hello cccc
PS F:\nodejs\Node_test> node .\13.回调地狱.js
hello aaaa
hello bbbb
hello cccc
PS F:\nodejs\Node_test> node .\13.回调地狱.js
hello aaaa
hello bbbb
hello cccc
因此想要有顺序的读取异步操作中的文件 就需要回调函数嵌套,但这样的代码 看起来很乱且维护性低
为了解决这样的问题,(回调地狱嵌套) 这时我们就需要用到
promise
Promise
参考文档 promise 阮一峰 和 promise MDN
为什么需要用promise?
我个人理解有三点:
- 在使用ajax调用成功和失败方法时,命名不够规范。
- 容易出现回调地狱
- 很难进行错误处理
- 解决异步
promise是什么?
打印出来看看:
很显然,promise是一个构造函数,它除了自身有 resolve 、 reject、 all 、race方法等,原型上还有then 、catch 等我们最常用到的方法。 所以我们 new promise 里面是肯定有 then 、 catch 方法的。
如何创建一个promise
function runAsync() {
const p = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
console.log('执行成功');
resolve('数据')
}, 1000)
})
return p
}
runAsync()
其中Promise构造函数接受一个参数,就是函数。并且会传入俩个参数:resolve,reject 。 分别表示异步操作执行成功后的回调函数和执行失败后的回调函数。
我们将包装一个函数,把promise放在里面并返回它, 这样做的意义在于我们可以使用promise原型上的then,catch方法
runAsync().then(function(data){
console.log(data);
//后面可以用传过来的数据做些其他操作
});
效果展示
以上就是Promise的作用了,简单的说,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式操作的形式执行回调函数。
promise.all 用法
all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完毕后才执行回调。
请看下面例子:
function runAsync1() {
const p = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
console.log('执行成功');
resolve('数据1')
}, 1000)
})
return p
}
function runAsync2() {
const p = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
console.log('执行成功');
resolve('数据2')
}, 1500)
})
return p
}
function runAsync3() {
const p = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
console.log('执行成功');
resolve('数据3')
}, 2000)
})
return p
}
Promise
.all([
runAsync1(),
runAsync2(),
runAsync3()
])
.then(result => console.log(result))
我们用Promise.all 来执行, 并且接受三个数组,里面的值都返回的是Promise对象,然后这三个异步操作并行执行,等它们都执行完毕后,数据才会在then方法里面。即all方法会把所有异步操作的结果放在一个属猪中再传给then。
Promise.race 用法
用法和Promise.all 一样,只不过all方法是谁跑得慢,就以谁为准执行回调, 而race方法就是谁跑得快,就以谁为准执行回调
将上面展示的代码 的all方法换成race看看是什么效果
Promise
.race([
runAsync1(),
runAsync2(),
runAsync3()
])
.then(result => console.log(result))
瞧!他会去优先输出最快(1s)的runAsync1函数的数据,
原因是因为异步处理问题,runAsync1函数的数据并不会等待上面的Promise执行完在开始执行,所以由于时间延迟,优先输出‘数据1’。