public
Authored by
罗彬

promise使用
async & await
演示项目:web版的简单爬虫
需求
下面我们写个html的爬虫,给一个页面url,爬出这个页面的所有静态文件到本地放着。
- 尽可能并行下载;
- 相同的文件只需要一次下载,内容用同一份;
文件结构
文件的层次见data目录,下面是他们的依赖关系
- data/index.html
- b.js
- c.js
- d.js
- c.js
- d.js
- e.css
- g.png
- f.css
- g.png
- b.js
假设已经写好了一个依赖关系分析函数,参数是路径和内容,返回该文件直接依赖的文件路径数组
在实际环境中,除非你解析完那个文件,否则是不可能知道别人依赖哪些文件的。
function parseDepends(path, content) {}
// parseDepends("data/index.html") 返回:["data/b.js", "data/c.js", "data/e.css", "data/f.css"]
// parseDepends("data/b.js") 返回:["data/c.js", "data/d.js"]
// parseDepends("data/c.js") 返回:["data/d.js"]
// parseDepends("data/d.js") 返回:[]
// parseDepends("data/e.css") 返回:["data/g.png"]
// parseDepends("data/f.css") 返回:["data/g.png"]
// parseDepends("data/g.png") 返回:[]
实现细节
- 如果请求的文件已经下载过了,而且下载完成;
- 如果请求的文件已经请求过了,而且还在下载中;
- 如果请求的文件之前没有处理过。
进一步的需求
- 异常情况处理
- 每个文件都有几个url,哪个下载的快,就用那个;(Promise.race)
要点
-
Promise
- 同步值可用Promise.resolve返回
- Promise.then 链式调用
- Promise.all 等所有完成,下一步
- Promise.race 只要有一个完成,下一步(fetch 超时时间)
-
async/await
- 只有async函数才能使用await
- async函数返回promise
- await后可以跟任何promise对象(不仅仅是async函数)
- 简化promise的then
-
注意事项
- then函数
- 穿透: 如果then传递一个非函数对象,那么会直接穿过这个then
- 正确用法,then的参数需要一个函数,他:
- return 另一个 promise
- return 一个同步的值 (或者 undefined)
- throw 一个同步异常
- 异常的处理:catch
- 同一个函数,如果返回promise,那么就统一返回promise
- Promise.resolve
- Promise.reject
- 性能,依赖分析
- promise一经创建,流程就已经开始了,用then或await可以随时取结果。
- await一定会等待
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// ============ imports
// ============ exports
/**
* 给定一个html路径,返回这个htmlPath中所有递归依赖的文件内容
*
* 知识点: 在某个层次中,将promise的侵入特性中断掉
* + promise和非promise的衔接点: 回调函数
* + async函数和非async的衔接点: promise
*/
export const run = (htmlPath, callback) => {
fecthRecursive(htmlPath).then(content => {
callback(content);
});
}
// ============ imlements
/**
* 内容缓冲
* 键:路径
* 值:该文件内容请求的promise
*/
let cache = new Map();
/**
* 模拟文件解析过程,返回该文件静态依赖的其他文件路径
*/
const parseDepends = (path, content) => {
const depends = {
"data/index.html": ["data/b.js", "data/c.js", "data/e.css", "data/f.css"],
"data/b.js": ["data/c.js", "data/d.js"],
"data/c.js": ["data/d.js"],
"data/e.css": ["data/g.png"],
"data/f.css": ["data/g.png"],
"data/d.js": [],
"data/g.png": [],
};
return depends[path];
}
/**
* 获取文件
* 由于返回的是内容,所以需要await
*/
const fetchFile = async (path) => {
let content;
if (cache.has(path)) {
// 知识点:promise如果已经完成,无论用多少次,都会返回相同的结果(异步)
content = await cache.get(path);
} else {
let p = fetch(path).then(res => res.arrayBuffer());
cache.set(path, p);
content = await p;
}
return content;
}
/**
* 递归处理
*/
const fecthRecursive = async (path) => {
// 无论如何,都要等内容处理完才能分析依赖
let content = await fetchFile(path);
let dependPaths = parseDepends(path, content);
let promises = dependPaths.map(elem => {
// 知识点:能不能这么写:return await fecthRecursive(elem);
return fecthRecursive(elem);
});
let childrenContents = await Promise.all(promises);
let children = {};
for (let i = 0; i < dependPaths.length; ++i) {
let key = dependPaths[i];
let content = childrenContents[i];
children[key] = content;
}
return {
path,
content,
children,
};
}
Please register or sign in to comment