public
Authored by avatar 罗彬

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

假设已经写好了一个依赖关系分析函数,参数是路径和内容,返回该文件直接依赖的文件路径数组

在实际环境中,除非你解析完那个文件,否则是不可能知道别人依赖哪些文件的。


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)

要点

  1. Promise

    • 同步值可用Promise.resolve返回
    • Promise.then 链式调用
    • Promise.all 等所有完成,下一步
    • Promise.race 只要有一个完成,下一步(fetch 超时时间)
  2. async/await

    • 只有async函数才能使用await
    • async函数返回promise
    • await后可以跟任何promise对象(不仅仅是async函数)
      • 简化promise的then
  3. 注意事项

  • then函数
    • 穿透: 如果then传递一个非函数对象,那么会直接穿过这个then
    • 正确用法,then的参数需要一个函数,他:
      • return 另一个 promise
      • return 一个同步的值 (或者 undefined)
      • throw 一个同步异常
  • 异常的处理:catch
  • 同一个函数,如果返回promise,那么就统一返回promise
    • Promise.resolve
    • Promise.reject
  1. 性能,依赖分析
  • promise一经创建,流程就已经开始了,用then或await可以随时取结果。
  • await一定会等待
2.30 KiB

// ============ 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,
    };
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment