回调地狱(callback hell) 和 promise

1. callback hell 回调地狱

1.1 没有顺序的读取 a b c 三个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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);
})

1
2
3
4
5
6
7
8
9
10
11
12
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

1.2 想要有顺序的分别 读取 a b c 三个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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);
})
})

})
1
2
3
4
5
6
7
8
9
10
11
12
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

想要有顺序的读取异步操作中的文件 就需要回调函数嵌套,但这样的代码 看起来很乱且维护性低

为了解决这样的问题,(回调地狱嵌套) 这时我们就需要用ES6中的API promise

Promise

参考文档 promise 阮一峰promise MDN

为什么需要用promise?

我个人理解有三点:

  1. 在使用ajax调用成功和失败方法时,命名不够规范。
  2. 容易出现回调地狱
  3. 很难进行错误处理
  4. 解决异步

promise是什么?

打印出来看看:
promise是什么

很显然,promise是一个构造函数,它除了自身有 resolve 、 reject、 all 、race方法等,原型上还有then 、catch 等我们最常用到的方法。 所以我们 new promise 里面是肯定有 then 、 catch 方法的。

如何创建一个promise

1
2
3
4
5
6
7
8
9
10
11
12
function runAsync() {
const p = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
console.log('执行成功');
resolve('数据')
}, 1000)
})
return p
}

runAsync()

其中Promise构造函数接受一个参数,就是函数。并且会传入俩个参数:resolve,reject 。 分别表示异步操作执行成功后的回调函数和执行失败后的回调函数

我们将包装一个函数,把promise放在里面并返回它, 这样做的意义在于我们可以使用promise原型上的then,catch方法

1
2
3
4
runAsync().then(function(data){
console.log(data);
//后面可以用传过来的数据做些其他操作
});

效果展示
效果展示

以上就是Promise的作用了,简单的说,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式操作的形式执行回调函数。

promise.all 用法

all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完毕后才执行回调。

请看下面例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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。

call方法

Promise.race 用法

用法和Promise.all 一样,只不过all方法是谁跑得慢,就以谁为准执行回调, 而race方法就是谁跑得快,就以谁为准执行回调

将上面展示的代码 的all方法换成race看看是什么效果

1
2
3
4
5
6
7
Promise
.race([
runAsync1(),
runAsync2(),
runAsync3()
])
.then(result => console.log(result))

 Promise.race

瞧!他会去优先输出最快(1s)的runAsync1函数的数据,

原因是因为异步处理问题,runAsync1函数的数据并不会等待上面的Promise执行完在开始执行,所以由于时间延迟,优先输出‘数据1’。