回调地狱问题的由来
需求: 封装一个方法,给定文件路径,可以读取文件内容,并把文件内容返回,如果错误返回错误结果
1 | const fs = require('fs') |
2 | const path = require('path') |
3 | |
4 | // 普通读取文件的方式 |
5 | function getFileByPath(fpath){ |
6 | fs.readFile(fpath,'utf-8',(err,data) => { |
7 | if(err) throw err |
8 | console.log(data) |
9 | return data |
10 | }) |
11 | } |
12 | |
13 | let result = getFileByPath(path.join(__dirname,'.files/1.txt')) |
14 | console.log(result) |
15 | ``` |
16 |
|
17 |
|
18 | 这样确实可以读到文件内容,但是`result`却是`undefined`,因为`readFiles`是异步读取的 |
19 |
|
20 | 于是想到在调用`getFileByPath`时添加回调函数 |
21 |
|
22 | ```js |
23 | function getFileByPath(fpath,callback){ |
24 | fs.readFile(fpath,'utf-8',(err,data) => { |
25 | if(err) throw err |
26 | callback(data) |
27 | }) |
28 | } |
29 | |
30 | getFileByPath(path.join(__dirname,'.files/1.txt'),(data)=>{ |
31 | console.log(data) |
32 | }) |
33 | ``` |
34 |
|
35 | 但是如果error了,`readFile`里的`callback`就不执行了, |
36 | 需要将成功的结果和失败的结果都返回给用户,所以添加两个回调函数,一个成功调用`succCb`,一个失败调用`errCb` |
37 |
|
38 | ```js |
39 | function getFileByPath(fpath,succCb, errCb){ |
40 | fs.readFile(fpath,'utf-8',(err,data) => { |
41 | if(err) return errCb |
42 | succCb |
43 | }) |
44 | } |
45 | |
46 | getFileByPath(path.join(__dirname,'.files/1.txt'),(data)=>{ |
47 | console.log(data) |
48 | },(err)=>{ |
49 | console.log(err) |
50 | }) |
51 | ``` |
52 |
|
53 | 看起来很完美呢,如果这时候再加个需求呢,读取文件1成功后,再读取文件2,读取文件2成功后再读取文件3,那么代码将会是下面的样子 |
54 |
|
55 | getFileByPath(path.join(__dirname,'.files/1.txt'),(data1)=>{ |
56 | console.log(data1) |
57 |
|
58 | // 读取文件2 |
59 | getFileByPath(path.join(__dirname,'files/2.txt'),(data2)=>{ |
60 | console.log(data2) |
61 |
|
62 | // 读取文件3 |
63 | getFileByPath(path.join(__dirname,'files/3.txt'),(data3)=>{ |
64 | console.log(data3) |
65 | }) |
66 | }) |
67 | }) |
68 |
|
69 | 哈哈,层层缩进,如果后面还要读取很多文件呢,那么就陷入了回调地狱,看看下面这张图,很形象有木有 |
70 |
|
71 | {% asset_img callback_hell.jpg 回调地狱 %} |
72 |
|
73 | ## 用Promise解决回调地狱问题 |
74 |
|
75 | 如果不想让代码变成酱紫,我们就开始使用Promise,其实Promise的本质就是单纯为了解决回调地狱问题,它会让代码变的优雅,并不会减少代码量 |
76 |
|
77 | > console.log(Promise) |
78 | ƒ Promise() { [native code] } |
79 |
|
80 | 可以看出来Promise是一个构造函数,我们通过new Promise可以生成一个Promise的实例 |
81 |
|
82 | 关于Promise更详细的说明 [Promise](http://es6.ruanyifeng.com/?search=fetch&x=0&y=0#docs/promise) |
83 |
|
84 | 如果用Promise来实现读取三个文件的需求呢 |
85 |
|
86 | 首先构建一个方法,返回一个promise |
87 |
|
88 | function getFileByPath(fpath){ |
89 | var promise = new Promise(resolve,reject){ |
90 | fs.readFile(fpath,'utf-8',(err,data)=>{ |
91 | if(err) return reject(err) |
92 | resolve(data) |
93 | }) |
94 | } |
95 | return promise |
96 | } |
97 |
|
98 | 开始读取文件 |
99 |
|
100 | getFileByPath('files1.txt').then((data1)=>{ |
101 | console.log(data1) |
102 |
|
103 | getFileByPath('files2.txt').then((data2)=>{ |
104 | console.log(data2) |
105 |
|
106 | getFileByPath('files3.txt').then((data3)=>{ |
107 | console.log(data3) |
108 | }) |
109 | }) |
110 | }) |
111 |
|
112 | 咦,写完了,这好像还是回调地狱啊,纳尼? |
113 |
|
114 | getFileByPath('files1.txt').then( data1 => { |
115 | console.log(data1) |
116 | return getFileByPath('files2.txt') |
117 | }).then( data2 => { |
118 | console.log(data2) |
119 | return getFileByPath('file3.txt') |
120 | }).then( data3 => { |
121 | console.log(data3) |
122 | }) |
123 |
|
124 | emmmmm...,正确打开方式是这样,是不是很优雅 |
125 |
|
126 | 但是为什么第二个`then`会获取到`getFileByPath('files2.txt')`的结果呢? |
127 |
|
128 | > 上面代码中,第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,就调用第一个回调函数,如果状态变为rejected,就调用第二个回调函数。 |