Skip to content

Commit 32016cf

Browse files
committed
update
1 parent 8f50096 commit 32016cf

File tree

5 files changed

+299
-3
lines changed

5 files changed

+299
-3
lines changed

part3-promise/02-promise-use.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ result.then(data => {
7272

7373
## 异常捕获
7474

75-
我们知道`then`会接收两个参数(函数),第一个参数会在执行`resolve`之后触发(还能传递参数),第二个参数会在执行`reject`之后触发(其实也可以传递参数,和`resolve`传递参数一样),但是上面的例子中,**我们没有用到`then`的第二个参数。这是为何呢 ———— 因为我没不建议这么用**
75+
我们知道`then`会接收两个参数(函数),第一个参数会在执行`resolve`之后触发(还能传递参数),第二个参数会在执行`reject`之后触发(其实也可以传递参数,和`resolve`传递参数一样),但是上面的例子中,**我们没有用到`then`的第二个参数。这是为何呢 ———— 因为不建议这么用**
7676

7777
对于`Promise`中的异常处理,我们建议用`catch`方法,而不是`then`的第二个参数。请看下面的代码,以及注释。
7878

part3-promise/03-promise-standard.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,60 @@
11
# 对标一下 Promise/A+ 规范
22

3+
Promise/A 是由 CommonJS 组织制定的异步模式编程规范,后来又经过一些升级,就是当前的 Promise/A+ 规范。上一节讲述的`Promise`的一些功能实现,就是根据这个规范来的。
34

5+
## 本节内容概述
6+
7+
- 介绍规范的核心内容
8+
- 状态变化
9+
- `then`方法
10+
- 接下来...
11+
12+
## 介绍规范的核心内容
13+
14+
网上有很多介绍 Promise/A+ 规范的文章,大家可以搜索来看,但是它的核心要点有以下几个,我也是从看了之后自己总结的
15+
16+
**关于状态**
17+
18+
- promise 可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)
19+
- promise 的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换
20+
21+
**关于`then`方法**
22+
23+
- promise 必须实现`then`方法,而且`then`必须返回一个 promise ,同一个 promise 的`then`可以调用多次(链式),并且回调的执行顺序跟它们被定义时的顺序一致
24+
- `then`方法接受两个参数,第一个参数是成功时的回调,在 promise 由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在 promise 由“等待”态转换到“拒绝”态时调用
25+
26+
下面挨个介绍这些规范在上一节代码中的实现,所谓理论与实践相结合。**在阅读以下内容时,你要时刻准备参考上一节的代码**
27+
28+
## 状态变化
29+
30+
> promise 可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)
31+
32+
拿到上一节的`readFilePromise`函数,然后执行`const result = readFilePromise(someFileName)`会得到一个`Promise`对象。
33+
34+
- 刚刚创建时,就是 等待(pending)状态
35+
- 如果读取文件成功了,`readFilePromise`函数内部的`callback`中会自定调用`resolve()`,这样就变为 已完成(fulfilled)状态
36+
- 如果很不幸读取文件失败了(例如文件名写错了,找不到文件),`readFilePromise`函数内部的`callback`中会自定调用`reject()`,这样就变为 已拒绝(rejeced)状态
37+
38+
> promise 的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换
39+
40+
这个规则还是可以参考读取文件的这个例子。从一开始准备读取,到最后无论是读取成功或是读取失败,都是不可逆的。另外,读取成功和读取失败之间,也是不能互换的。这个逻辑没有任何问题,很好理解。
41+
42+
## `then`方法
43+
44+
> promise 必须实现`then`方法,而且`then`必须返回一个 promise ,同一个 promise 的`then`可以调用多次(链式),并且回调的执行顺序跟它们被定义时的顺序一致
45+
46+
- `promise`对象必须实现`then`方法这个无需解释,没有`then`那就不叫`promise`
47+
- “而且`then`必须返回一个`promise`,同一个 promise 的`then`可以调用多次(链式)” ———— 这两句话说明了一个意思 ———— `then`肯定要再返回一个`promise`,要不然`then`后面怎么能再链式的跟一个`then`呢?
48+
49+
> `then`方法接受两个参数,第一个参数是成功时的回调,在 promise 由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在 promise 由“等待”态转换到“拒绝”态时调用
50+
51+
这句话比较好理解了,我们从一开始就在 demo 中演示。
52+
53+
## 接下来...
54+
55+
`Promise`的应用、规范都介绍完了,看起来挺牛的,也解决了异步操作中使用`callback`带来的很多问题。但是`Promise`本质上到底是一种什么样的存在,它是真的把`callback`弃而不用了吗,还是两者有什么合作关系?它到底是真的神通广大,还是使用了障眼法?
56+
57+
这些问题,大家学完`Promise`之后应该去思考,不能光学会怎么用就停止了。下一节我们一起来探讨~
458

559
## 求打赏
660

part3-promise/04-promise-callback.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,82 @@
11
# Promise 真的取代 callback 了吗
22

3+
Promise 虽然改变了 JS 工程师对于异步操作的写法,但是却改变不了 JS 单线程、异步的执行模式。
34

5+
## 本节概述
6+
7+
- JS 异步的本质
8+
- Promise 只是表面的写法上的改变
9+
- Promise 中不能缺少 callback
10+
- 接下来...
11+
12+
## JS 异步的本质
13+
14+
从最初的 ES3、4 到 ES5 再到现在的 ES6 和即将到来的 ES7,语法标准上更新很多,但是 JS 这种单线程、异步的本质是没有改变的。nodejs 中读取文件的代码一直都可以这样写
15+
16+
```javascript
17+
fs.readFile('some.json', (err, data) => {
18+
})
19+
```
20+
21+
既然异步这个本质不能改变,伴随异步在一起的永远都会有`callback`,因为没有`callback`就无法实现异步。因此`callback`永远存在。
22+
23+
## Promise 只是表面的写法上的改变
24+
25+
JS 工程师不会讨厌 JS 异步的本质,但是很讨厌 JS 异步操作中`callback`的书写方式,特别是遇到万恶的`callback-hell`(嵌套`callback`)时。
26+
27+
计算机的抽象思维和人的具象思维是完全不一样的,人永远喜欢看起来更加符合逻辑、更加易于阅读的程序,因此现在特别强调代码可读性。而`Promise`就是一种代码可读性的变化。大家感受一下这两种不同(**这其中还包括异常处理,加上异常处理会更加复杂**
28+
29+
第一种,传统的`callback`方式
30+
31+
```javascript
32+
fs.readFile('some1.json', (err, data) => {
33+
fs.readFile('some2.json', (err, data) => {
34+
fs.readFile('some3.json', (err, data) => {
35+
fs.readFile('some4.json', (err, data) => {
36+
37+
})
38+
})
39+
})
40+
})
41+
```
42+
43+
第二种,`Promise`方式
44+
45+
```javascript
46+
readFilePromise('some1.json').then(data => {
47+
return readFilePromise('some2.json')
48+
}).then(data => {
49+
return readFilePromise('some3.json')
50+
}).then(data => {
51+
return readFilePromise('some4.json')
52+
})
53+
```
54+
55+
这两种方式对于代码可读性的对比,非常明显。**但是最后再次强调,`Promise`只是对于异步操作代码可读性的一种变化,它并没有改变 JS 异步执行的本质,也没有改变 JS 中存在`callback`的现象**
56+
57+
## Promise 中不能缺少 callback
58+
59+
上文已经基本给出了上一节提问的答案,但是这里还需要再加一个补充:`Promise`不仅仅是没有取代`callback`或者弃而不用,反而`Promise`中要使用到`callback`。因为,JS 异步执行的本质,必须有`callback`存在,否则无法实现。
60+
61+
再次粘贴处之前章节的封装好的一个`Promise`函数(进行了一点点简化)
62+
63+
```javascript
64+
const readFilePromise = function (fileName) {
65+
return new Promise((resolve, reject) => {
66+
fs.readFile(fileName, (err, data) => {
67+
resolve(data.toString())
68+
})
69+
})
70+
}
71+
```
72+
73+
上面的代码中,`promise`对象的状态要从`pending`变化为`fulfilled`,就需要去执行`resolve()`函数。那么是从哪里执行的 ———— **还得从`callback`中执行`resolve`函数 ———— 这就是`Promise`也需要`callback`的最直接体现**
74+
75+
## 接下来...
76+
77+
一块技术“火”的程度和第三方开源软件的数量、质量以及使用情况有很大的正比关系。例如为了简化 DOM 操作,jquery 风靡全世界。Promise 用的比较多,第三方库当然就必不可少,它们极大程度的简化了 Promise 的代码。
78+
79+
接下来我们一起看看`Q.js`这个库的使用,学会了它,将极大程度提高你写 Promise 的效率。
480

581
## 求打赏
682

part3-promise/05-promise-q.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,149 @@
11
# 使用 Q.js 库
22

3+
如果实际项目中使用`Promise`,还是强烈建议使用比较靠谱的第三方插件,会极大增加你的开发效率。除了将要介绍的`Q.js`,还有`bluebird`也推荐使用,去 github 自行搜索吧。
34

5+
另外,使用第三方库不仅仅是提高效率,**它还让你在浏览器端(不支持`Promise`的环境中)使用`promise`**
46

7+
本节展示的代码参考[这里](./test.js)
8+
9+
## 本节内容概述
10+
11+
- 下载和安装
12+
- 使用`Q.nfcall``Q.nfapply`
13+
- 使用`Q.defer`
14+
- 使用`Q.denodeify`
15+
- 使用`Q.all``Q.any`
16+
- 使用`Q.delay`
17+
- 其他
18+
19+
## 下载和安装
20+
21+
可以直接去它的 [github 地址](https://github.com/kriskowal/q) (近 1.3W 的 star 数量说明其用户群很大)查看文档。
22+
23+
如果项目使用 CommonJS 规范直接 `npm i q --save`,如果是网页外链可寻找可用的 cdn 地址,或者干脆下载到本地。
24+
25+
以下我将要演示的代码,都是使用 CommonJS 规范的,因此我要演示代码之前加上引用,以后的代码演示就不重复加了。
26+
27+
```javascript
28+
const Q = require('q')
29+
```
30+
31+
## 使用`Q.nfcall``Q.nfapply`
32+
33+
要使用这两个函数,你得首先了解 JS 的`call``apply`,如果不了解,先去看看。熟悉了这两个函数之后,再回来看。
34+
35+
`Q.nfcall`就是使用`call`的语法来返回一个`promise`对象,例如
36+
37+
```javascript
38+
const fullFileName = path.resolve(__dirname, '../data/data1.json')
39+
const result = Q.nfcall(fs.readFile, fullFileName, 'utf-8') // 使用 Q.nfcall 返回一个 promise
40+
result.then(data => {
41+
console.log(data)
42+
}).catch(err => {
43+
console.log(err.stack)
44+
})
45+
```
46+
47+
`Q.nfapply`就是使用`apply`的语法返回一个`promise`对象,例如
48+
49+
```javascript
50+
const fullFileName = path.resolve(__dirname, '../data/data1.json')
51+
const result = Q.nfapply(fs.readFile, [fullFileName, 'utf-8']) // 使用 Q.nfapply 返回一个 promise
52+
result.then(data => {
53+
console.log(data)
54+
}).catch(err => {
55+
console.log(err.stack)
56+
})
57+
```
58+
59+
怎么样,体验了一把,是不是比直接自己写`Promise`简单多了?
60+
61+
## 使用`Q.defer`
62+
63+
`Q.defer`算是一个比较偏底层一点的 API ,用于自己定义一个`promise`生成器,如果你需要在浏览器端编写,而且浏览器不支持`Promise`,这个就有用处了。
64+
65+
```javascript
66+
function readFile(fileName) {
67+
const defer = Q.defer()
68+
fs.readFile(fileName, (err, data) => {
69+
if (err) {
70+
defer.reject(err)
71+
} else {
72+
defer.resolve(data.toString())
73+
}
74+
})
75+
return defer.promise
76+
}
77+
readFile('data1.json')
78+
.then(data => {
79+
console.log(data)
80+
})
81+
.catch(err => {
82+
console.log(err.stack)
83+
})
84+
```
85+
86+
87+
## 使用`Q.denodeify`
88+
89+
我们在很早之前的一节中自己封装了一个`fs.readFile``promise`生成器,这里再次回顾一下
90+
91+
```javascript
92+
const readFilePromise = function (fileName) {
93+
return new Promise((resolve, reject) => {
94+
fs.readFile(fileName, (err, data) => {
95+
if (err) {
96+
reject(err)
97+
} else {
98+
resolve(data.toString())
99+
}
100+
})
101+
})
102+
}
103+
```
104+
105+
虽然看着不麻烦,但是还是需要很多行代码来实现,如果使用`Q.denodeify`,一行代码就搞定了!
106+
107+
```javascript
108+
const readFilePromise = Q.denodeify(fs.readFile)
109+
```
110+
111+
`Q.denodeif`就是一键将`fs.readFile`这种有回调函数作为参数的异步操作封装成一个`promise`生成器,非常方便!
112+
113+
## 使用`Q.all``Q.any`
114+
115+
这两个其实就是对应了之前讲过的`Promise.all``Promise.race`,而且应用起来一模一样,不多赘述。
116+
117+
```javascript
118+
const r1 = Q.nfcall(fs.readFile, 'data1.json', 'utf-8')
119+
const r2 = Q.nfcall(fs.readFile, 'data2.json', 'utf-8')
120+
Q.all([r1, r2]).then(arr => {
121+
console.log(arr)
122+
}).catch(err => {
123+
console.log(err)
124+
})
125+
```
126+
127+
## 使用`Q.delay`
128+
129+
`Q.delay`,顾名思义,就是延迟的意思。例如,读取一个文件成功之后,再过五秒钟之后,再去做xxxx。这个如果是自己写的话,也挺费劲的,但是`Q.delay`就直接给我们分装好了。
130+
131+
```javascript
132+
const result = Q.nfcall(fs.readFile, 'data1.json', 'utf-8')
133+
result.delay(5000).then(data => {
134+
// 得到结果
135+
console.log(data.toString())
136+
}).catch(err => {
137+
// 捕获错误
138+
console.log(err.stack)
139+
})
140+
```
141+
142+
## 其他
143+
144+
以上就是`Q.js`一些最常用的操作,其他的一些非常用技巧,大家可以去搜索或者去官网查看文档。
145+
146+
至此,ES6 `Promise`的所有内容就已经讲完了。但是异步操作的优化到这里没有结束,更加精彩的内容还在后面 ———— `Generator`
5147

6148
## 求打赏
7149

part3-promise/test.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const fs = require('fs')
22
const path = require('path')
3+
const Q = require('q')
34

45
// 封装一个 Promise
56
const readFilePromise = function (fileName) {
@@ -104,7 +105,7 @@ function fn4() {
104105

105106

106107
// ----- Promise.resolve() 的使用 -----
107-
function fnC() {
108+
function fn5() {
108109

109110
// Promise.resolve('foo') 的写法等价于以下代码,
110111
// new Promise( resolve => resolve('foo') )
@@ -132,6 +133,29 @@ function fnC() {
132133
// 这里的 deferred 对象就是一个 thenable 对象
133134
// var jsPromise = Promise.resolve($.ajax('/whatever.json'));
134135
}
135-
fnC()
136+
// fn5()
137+
138+
139+
// ------ Q.nfcall 和 Q.nfapply --------
140+
function fn6() {
141+
142+
const fullFileName1 = path.resolve(__dirname, '../data/data1.json')
143+
const result1 = Q.nfcall(fs.readFile, fullFileName1, 'utf-8')
144+
result1.then(data => {
145+
console.log(data)
146+
}).catch(err => {
147+
console.log(err.stack)
148+
})
149+
150+
const fullFileName2 = path.resolve(__dirname, '../data/data2.json')
151+
const result2 = Q.nfapply(fs.readFile, [fullFileName2, 'utf-8']) // 使用 Q.nfapply 返回一个 promise
152+
result2.then(data => {
153+
console.log(data)
154+
}).catch(err => {
155+
console.log(err.stack)
156+
})
157+
158+
}
159+
fn6()
136160

137161

0 commit comments

Comments
 (0)