Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 3a18abf

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

File tree

7 files changed

+419
-4
lines changed

7 files changed

+419
-4
lines changed

‎02-04-promise-advanced.md‎

Lines changed: 348 additions & 1 deletion
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‎

Lines changed: 1 addition & 1 deletion
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‎

Lines changed: 32 additions & 0 deletions
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)

AltStyle によって変換されたページ (->オリジナル) /