Skip to content

Commit 3a18abf

Browse files
committed
接近完成
1 parent 3f49c43 commit 3a18abf

7 files changed

+419
-4
lines changed

02-04-promise-advanced.md

+348-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,349 @@
11
Promise 进阶
2-
========
2+
========
3+
4+
接下来的时间,我会继续把 Promise 的知识补完。
5+
6+
## `Promise.reject(reason)`
7+
8+
这个方法比较简单,就返回一个状态为 `rejected` 的 Promise 实例。
9+
10+
它接受一个参数,为了方便后续操作,最好创建一个 Error 实例。
11+
12+
## `Promise.all()`
13+
14+
`Promise.all([p1, p2, p3, ....])` 用于将多个 Promise 实例,包装成一个新的 Promise 实例。
15+
16+
它接受一个数组作为参数,数组里可以是 Promise 对象,也可以是别的值,这些值都会交给 `Promise.resolve()` 处理。当所有子 Promise 都完成,该 Promise 完成,返回值是包含全部返回值的数组。有任何一个失败,该 Promise 失败,`.catch()` 得到的是第一个失败的子 Promise 的错误。
17+
18+
```javascript
19+
Promise.all([1, 2, 3])
20+
.then( all => {
21+
console.log('1: ', all);
22+
return Promise.all([ function () {
23+
console.log('ooxx');
24+
}, 'xxoo', false]);
25+
})
26+
.then( all => {
27+
console.log('2: ', all);
28+
let p1 = new Promise( resolve => {
29+
setTimeout(() => {
30+
resolve('I\'m P1');
31+
}, 1500);
32+
});
33+
let p2 = new Promise( resolve => {
34+
setTimeout(() => {
35+
resolve('I\'m P2');
36+
}, 1450)
37+
});
38+
return Promise.all([p1, p2]);
39+
})
40+
.then( all => {
41+
console.log('3: ', all);
42+
let p1 = new Promise( resolve => {
43+
setTimeout(() => {
44+
resolve('I\'m P1');
45+
}, 1500);
46+
});
47+
let p2 = new Promise( (resolve, reject) => {
48+
setTimeout(() => {
49+
reject('I\'m P2');
50+
}, 1000);
51+
});
52+
let p3 = new Promise( (resolve , reject) => {
53+
setTimeout(() => {
54+
reject('I\'m P3');
55+
}, 3000);
56+
});
57+
return Promise.all([p1, p2, p3]);
58+
})
59+
.then( all => {
60+
console.log('all', all);
61+
})
62+
.catch( err => {
63+
console.log('Catch: ', err);
64+
});
65+
66+
// 输出:
67+
// 1: [ 1, 2, 3 ]
68+
// 2: [ [Function], 'xxoo', false ]
69+
// 3: [ 'I\'m P1', 'I\'m P2' ]
70+
// Catch: I'm P2
71+
```
72+
73+
这里很容易懂,就不一一解释了。
74+
75+
### 常见用法
76+
77+
`Promise.all()` 最常见就是和 `.map()` 连用。
78+
79+
我们改造一下前面的例子。
80+
81+
```javascript
82+
// FileSystem.js
83+
const fs = require('fs');
84+
85+
module.exports = {
86+
readDir: function (path, options) {
87+
return new Promise( resolve => {
88+
fs.readdir(path, options, (err, files) => {
89+
if (err) {
90+
throw err;
91+
}
92+
resolve(files);
93+
});
94+
});
95+
},
96+
stat: function (path, options) {
97+
return new Promise( resolve => {
98+
fs.stat(path, options, (err, stat) => {
99+
if (err) {
100+
throw err;
101+
}
102+
resolve(stat);
103+
});
104+
});
105+
}
106+
};
107+
108+
// main.js
109+
const fs = require('./FileSystem');
110+
111+
function findLargest(dir) {
112+
return fs.readDir(dir, 'utf-8')
113+
.then( files => {
114+
return Promise.all( files.map( file => fs.stat(file) ));
115+
})
116+
.then( stats => {
117+
let biggest = stats.reduce( (memo, stat) => {
118+
if (stat.isDirectory()) {
119+
return memo;
120+
}
121+
if (memo.size < stat.size) {
122+
return stat;
123+
}
124+
return memo;
125+
});
126+
return biggest.file;
127+
})
128+
.catch(console.log.bind(console));
129+
}
130+
131+
findLargest('some/path/')
132+
.then( file => {
133+
console.log(file);
134+
})
135+
.catch( err => {
136+
console.log(err);
137+
});
138+
```
139+
140+
在这个例子当中,我使用 Promise 将 `fs.stat``fs.readdir` 进行了封装,让其返回 Promise 对象。然后使用 `Promise.all()` + `array.map()` 方法,就可以对其进行遍历,还可以避免使用外层作用域的变量。
141+
142+
## `Promise.race()`
143+
144+
`Promise.race()` 的功能和用法与 `Promise.all()` 十分类似,也接受一个数组作为参数,然后把数组里的值都用 `Promise.resolve()` 处理成 Promise 对象,然后再返回一个新的 Promise 实例。只不过这些子 Promise 有任意一个完成,`Promise.race()` 返回的 Promise 实例就算完成,并且返回完成的子实例的返回值。
145+
146+
它最常见的用法,是作为超时检查。我们可以把异步操作和定时器放在一个 `Promise.race()` 里,如果定时器触发的时候异步操作还没返回,就可以认为超时了。
147+
148+
```javascript
149+
let p1 = new Promise(resolve => {
150+
// 这是一个长时间的调用,我们假装它就是正常要跑的异步操作
151+
setTimeout(() => {
152+
resolve('I\'m P1');
153+
}, 10000);
154+
});
155+
let p2 = new Promise(resolve => {
156+
// 这是个稍短的调用,假装是一个定时器
157+
setTimeout(() => {
158+
resolve(false);
159+
}, 2000)
160+
});
161+
Promise.race([p1, p2])
162+
.then(value => {
163+
if (value) {
164+
console.log(value);
165+
} else {
166+
console.log('Timeout, Yellow flower is cold');
167+
}
168+
});
169+
170+
// 输出:
171+
// Timeout, Yellow flower is cold
172+
```
173+
174+
## Promise 嵌套
175+
176+
这种情况在初用 Promise 的同学的代码中很常见,大概是这么个意思:
177+
178+
```javascript
179+
new Promise( resolve => {
180+
console.log('Step 1');
181+
setTimeout(() => {
182+
resolve(100);
183+
}, 1000);
184+
})
185+
.then( value => {
186+
return new Promise(resolve => {
187+
console.log('Step 1-1');
188+
setTimeout(() => {
189+
resolve(110);
190+
}, 1000);
191+
})
192+
.then( value => {
193+
console.log('Step 1-2');
194+
return value;
195+
})
196+
.then( value => {
197+
console.log('Step 1-3');
198+
return value;
199+
});
200+
})
201+
.then(value => {
202+
console.log(value);
203+
console.log('Step 2');
204+
});
205+
```
206+
207+
因为 `.then()` 返回的也是 Promise 实例,所以外层的 Promise 会等里面的 `.then()` 执行完再继续执行,所以这里的执行顺序稳定为“1 > 1-1 > 1-2 > 1-3 > 2”。但是从阅读体验和维护效率的角度来看,最好把它展开:
208+
209+
```javascript
210+
new Promise( resolve => {
211+
console.log('Step 1');
212+
setTimeout(() => {
213+
resolve(100);
214+
}, 1000);
215+
})
216+
.then( value => {
217+
return new Promise(resolve => {
218+
console.log('Step 1-1');
219+
setTimeout(() => {
220+
resolve(110);
221+
}, 1000);
222+
});
223+
})
224+
.then( value => {
225+
console.log('Step 1-2');
226+
return value;
227+
})
228+
.then( value => {
229+
console.log('Step 1-3');
230+
return value;
231+
})
232+
.then(value => {
233+
console.log(value);
234+
console.log('Step 2');
235+
});
236+
```
237+
238+
## 队列
239+
240+
有时候我们不希望所有动作一起发生,而是按照一定顺序,逐个进行。这样的形式,就是队列。在我看来,队列是 Promise 的核心用法。即使在异步函数实装的今天,队列也有其独特的价值。
241+
242+
用 Promise 实现队列的方式很多,这里兹举两例:
243+
244+
```javascript
245+
// 使用 Array.prototype.forEach
246+
function queue(things) {
247+
let promise = Promise.resolve();
248+
things.forEach( thing => {
249+
// 这里很重要,如果不把 `.then()` 返回的新实例赋给 `promise` 的话,就不是队列,而是批量执行
250+
promise = promise.then( () => {
251+
return new Promise( resolve => {
252+
doThing(thing, () => {
253+
resolve();
254+
});
255+
});
256+
});
257+
});
258+
return promise;
259+
}
260+
261+
queue(['lots', 'of', 'things', ....]);
262+
263+
// 使用 Array.prototype.reduce
264+
function queue(things) {
265+
return things.reduce( (promise, thing) => {
266+
return promise.then( () => {
267+
return new Promise( resolve => {
268+
doThing(thing, () => {
269+
resolve();
270+
});
271+
});
272+
});
273+
}, Promise.resolve());
274+
}
275+
276+
queue(['lots', 'of', 'things', ....]);
277+
```
278+
279+
这个例子如此直接我就不再详细解释了。下面我们看一个相对复杂的例子,
280+
281+
> 假设需求:开发一个爬虫,抓取某网站。
282+
283+
```javascript
284+
function fetchAll(urls) {
285+
return urls.reduce((promise, url) => {
286+
return promise.then( () => {
287+
return fetch(url);
288+
});
289+
}, Promise.resolve());
290+
}
291+
function fetch(url) {
292+
return spider.fetch(url)
293+
.then( content => {
294+
return saveOrOther(content);
295+
})
296+
.then( content => {
297+
let links = spider.findLinks(content);
298+
return fetchAll(links);
299+
});
300+
}
301+
let url = ['http://blog.meathill.com/'];
302+
fetchAll(url);
303+
```
304+
305+
这段代码,我假设有一个蜘蛛工具(spider)包含基本的抓取和分析功能,然后循环使用 `fetch``fetchAll` 方法,不断分析抓取的页面,然后把页面当中所有的链接都加入到抓取队列当中。
306+
307+
### Generator
308+
309+
如果你了解 Generator,你应该知道 Generator 可以在执行时中断,并等待被唤醒。如果能把它们连到一起使用应该不错。
310+
311+
```javascript
312+
let generator = function* (urls) {
313+
let loaded = [];
314+
while (urls.length > 0) {
315+
let url = urls.unshift();
316+
yield spider.fetch(url)
317+
.then( content => {
318+
loaded.push(url);
319+
return saveOrOther(content);
320+
})
321+
.then( content => {
322+
let links = spider.findLinks(content);
323+
links = _.without(links, loaded);
324+
urls = urls.concat(links);
325+
});
326+
}
327+
return 'over';
328+
};
329+
330+
function fetch(urls) {
331+
let iterator = generator();
332+
333+
function next() {
334+
let result = iterator.next();
335+
if (result.done) {
336+
return result.value;
337+
}
338+
let promise = iterator.next().value;
339+
promise.then(next);
340+
}
341+
342+
next();
343+
}
344+
345+
let urls = ['http://blog.meathill.com'];
346+
fetch(urls);
347+
```
348+
349+
Generator 可以把所有待抓取的 URL 都放到一个数组里,然后慢慢加载。从整体来看,暴露给外界的 `fetch` 函数其实变简单了很多。但是实现 Generator 本身有点费工夫,其中的利弊大家自己权衡吧。

02-1-promise-basic.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ new Promise( resolve => {
8686

8787
### Promise.resolve()
8888

89-
这里必须补充一下 Promise.resolve() 的相关知识。它是 Promise 的静态方法,可以返回一个状态为 `fulfilled` 的 Promise 实例。
89+
这里必须补充一下 Promise.resolve() 的相关知识。它是 Promise 的静态方法,可以返回一个状态为 `fulfilled` 的 Promise 实例。这个方法非常重要,其它方法都需要用它处理参数。
9090

9191
它可以接受四种不同类型的参数,并且返回不同的值:
9292

03-1-async-function-vs-promise.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
Async Function VS Promise
2+
=======
3+
4+
## 异步函数的优势
5+
6+
异步函数对异步编程来说,是很大的进步。尤其在 `try/catch/throw` 方面,异步函数可以延续以前的标准写法,优势非常大。
7+
8+
## Promise 的优势
9+
10+
不过目前来看,Promise (暂时)并不会被完全取代。
11+
12+
### 异步函数依赖 Promise
13+
14+
异步函数返回的是一个 Promise 对象;`await` 关键字只能等待 Promise 对象完成操作。
15+
16+
所以关于 Promise 的知识都还有效,大家也必须学习 Promise,才能更好的使用异步函数。
17+
18+
### Promise 能更好的提供队列操作,并且在对象之间传递
19+
20+
Promise 本身是一个对象,所以可以任意传递。这意味着我们可以在程序的任何位置使用和维护队列,这会比其它方式都方便。
21+
22+
比如我们做一些办公产品,用户可能先做了 A,然后又做了 B。然而 A 和 B 之间需要很强的时间关联,顺序不能乱。这个时候,队列就比两次异步请求更合适。
23+
24+
### 异步函数的覆盖率还不够
25+
26+
从上一节最后的截图可以看到,虽然异步函数的支持率已经很高了,但是在 iOS 10.2、Android 4 等关键平台上,还没有原生实现。这就需要我们进行降级兼容。
27+
28+
然而异步函数的降级代码很难写,Babel 转译后至少要增加3000行,对于我们的应用来说是一个不小的代价。如果应用本身不大,或者异步操作并不复杂,可能用 Promise 是个更好的选择。
29+
30+
## 总结
31+
32+
根据需求和场景选择合适的技术,永远不会错。

03-1-difference-between-await-async-and-promise.md

Whitespace-only changes.

0 commit comments

Comments
 (0)