0%

小说章节合并插件-第三篇

小说合并插件 第三篇

接下来我们开始介绍promise对象的使用了,

我们的需求是这样, 获取小说列表第一页的内容后才能继续获取第二页的URL, 所以这个运行方式必须是同步的不能用异步运行。

如果你没有获取第一页, 那么你也不知道它的第二页内容, 这样的情况下不能异步运行, 遇到这样的情况该怎么办?

有人说这个好办啊, XMLHttpRequest.open第三个参数传一个false就可以了么!

如果真是这么轻而易举的解决问题的话人家也不会搞出promise对象, 也不会在es6里加入新的语法async/await, 更何况我们编写这个插件就是为了探索promise对象。。

promise对象解决回调地狱

所以还是要折腾我们的promise对象, 首先我们改写download函数, 把这个改变成promise对象。

代码里有重要注释

// 下载网页数据
// 如果不用promise对象的话需要两个回调函数, 有了promise后就不需要了
function download(url)
{
// 创建promise对象, 而promise对象接受一个参数, 这里我们编写业务逻辑, 所以我们叫这个函数为业务函数
// 而我们的业务函数本身也需要两个函数, 约定俗成称之为resolve和reject
return new Promise((resolve, reject) =>
{
// 正常的业务逻辑
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = () =>
{
if (xhr.readyState === 4)
{
if (xhr.status === 200)
{
// 业务函数成功后调用resolve, 而且把返回的数据作为参数传递进去, 相当于代替了以前的成功回调
resolve(xhr.responseText);
}
else
{
// 这个和上面相反, 业务函数出错后调用,
reject(new Error(`http ${xhr.status} ${xhr.statusText}`));
}
}
}
xhr.open("GET", url, true);
xhr.overrideMimeType(`Text/html; charset=${document.characterSet}`); // 让返回的文档字符编码保持和当前文档一致,
xhr.send();
});
}

然后我们编写一个通用的辅助函数, 作用是查询指定文本的url

function getTextURL(dom, className, urlText)
{
for (let el of dom.getElementsByClassName(className)[0].getElementsByTagName("a"))
{
if (el.innerText.includes(urlText))
{
return el.getAttribute("href");
break;
}
}
return null;
}

有了这个函数就看看promise对象如何把嵌套回调改造成顺序调用的。

代码里有重要注释

// 返回小说的数据 在main函数里调用
function novel()
{
let url = getTextURL(document, "ablum_read", "正序");
if (url === null)
{
alert("获取章节列表失败");
}
// 请注意download函数后面的then调用
download(url)
.then((doc) =>
{
let dom = document.createElement("div");
dom.innerHTML = doc;
let url = getTextURL(dom, "page", "下一页");
alert(url);
// 我们看download函数返回了一个promise对象而then就是它的一个方法, 所以如下我们在返回一个promise对象, 继续调用then方法
return download(url);
})
.then((doc) =>
{
let dom = document.createElement("div");
dom.innerHTML = doc;
let url = getTextURL(dom, "page", "下一页");
alert(url);
return download(url);
})
.then((doc) =>
{
let dom = document.createElement("div");
dom.innerHTML = doc;
let url = getTextURL(dom, "page", "下一页");
alert(url);
return download(url);
})
// 最后调用catch方法, 从名称中我们可以看出这是错误处理方法, 可以给他一个不存在的url看看运行结果比如/123
.catch(alert);
}

现在好了, 代码看着特别顺眼, 运行情况和嵌套回调一模一样, 但是容易理解容易调试。

promise对象的要点

我们可以把普通的异步函数用一个promise对象来包装, 可以对比两个版本的download函数, 在这个过程里有两个要点。

  • 1.必须搞清楚resolve、reject和then、catch的关系。

  • 2.then函数也应该返回一个promise对象, 这样我们可以继续调用then方法, 如果它返回一个null或者其他类型那么肯定没办法调用then了。

resolve和then

我们搞清楚前面两者的关系就可以了, reject和catch同样的原理。

我们知道resolve是给业务函数传入的函数, 业务函数成功后调用, 而且把业务函数的运行结果传入给该函数。

这样的话相当于调用了then给定的函数。

看看如下两个代码块的对比

// 传统的异步回调
// 定义
function download(url, callback)
{
.....
callback(result);
}
// 调用
download(url1, (result) =>
{
..........
download(url2, (result) =>
{
..........
// 理论上可以继续嵌套, 不过超过四五层后估计很难理解和维护这些代码了。 当然如果你是超人可以继续用这样的模式
});
});

同样的功能用promise对象如何编写。

// promise异步
// 定义
function download(url)
{
return new Promise((resolve, reject) =>
{
......
resolve(result);
});
}
// 调用
download(url1)
.then((result) =>
{
..........
return download(url2)
})
.then((result) =>
{
..........
});
// 可以写很多次then方法, 不会出现回调地狱

到此为止我们对promise对象有了初步理解, 接下来该动手继续我们的项目了, 未完待续……