以前写过一个分批预加载资源的插件,其实质即是串行执行异步,使用的方法是generator + promise ~~
前几天写了一个爬虫,抓取页面的n个页面的音频资源,其也是串行执行异步,可是在使用的async/await + promise,这里对两个方法作一下对比,会发现async/await将使得代码更为简洁。node
预加载的思路以下:
a、线性(串行)的控制每批次的资源加载,完成一批,再装载另外一批的处理;(按数组顺序实现每批次的加载)
b、实现单批资源的异步下载;(单批次内的资源异步下载,无需顺序)
c、实现单个资源的下载。编程
代码以下:数组
/* Created by hity on 06/08/17 参数说明: auto: 是否自执行 imgs: 需预加载的图片列表,为二维表 ignore:在自执行过程当中,须要跳过的图片set 脚标 firstSetReady: 第一组图片完成加载之后,置为true,便于外部掌握状态 说明: 非自执行的需求,可直接调用loadOneSetImages方法,返回值为promise */ //co为generator的线性处理函数(自执行),实质是g.next()执行权移交,yield后只能使用thunk 或者 promise import co from 'co' class Preload { constructor(auto, imgs = [], ignore = []) { this.imgs = imgs this.ignore = ignore this.auto = auto this.firstSetReady = false this.finished = false this.init() } init() { if (this.auto) { co(this.autoExeImageStream()).then((data) => { console.log('资源加载完毕~') this.finished = true }).catch(() => { console.log('资源加载出错~') this.finished = true }) } } // generator,每一个yield处理一批资源 * autoExeImageStream() { for (let i = 0; i < this.imgs.length; i++) { if (this.ignore.indexOf(i) == -1) { yield this.loadOneSetImages(this.imgs[i]) } } } // 使用promise.all获取每一个资源的加载结果 loadOneSetImages(imgList) { let promiseList = [] imgList.forEach((item) => { promiseList.push(this.loadSingleImage(item)) }) return Promise.all(promiseList).then((data) => { console.log('资源加载-1', data) this.firstSetReady = true }).catch(function() { this.firstSetReady = true }) } // 加载单个图片资源 经过promise实现 loadSingleImage(src) { if (!src) { return new Promise((resolve, reject) => { resolve('noImage') }) } let newImg = new Image() newImg.src = src return new Promise((resolve, reject) => { newImg.onload = () => { resolve('success') } newImg.onerror = () => { resolve('fail') } }) } } export default Preload
如上代码,思路b、c,每批次资源的异步加载,经过promise实现,其为实现异步的根本,经过loadSingleImage函数,实现单个资源的加载;经过loadOneSetImages函数,实现单批资源的异步加载,在async/await的改造过程当中,这两个部分不须要调整。promise
代码的简单实现以下:浏览器
init() { if (this.auto) { this.autoExeImageStream().then((data) => { console.log('资源加载完毕~') this.finished = true }).catch(() => { console.log('资源加载出错~') this.finished = true }) } } async autoExeImageStream() { for (let i = 0; i < this.imgs.length; i++) { if (this.ignore.indexOf(i) == -1) { try { await this.loadOneSetImages(this.imgs[i]) } catch (error) { console.log(error) } } } }
如上,仅需改变init函数内的autoExeImageStream函数的调用方式,再也不依赖co模块;autoExeImageStream为异步,里面的await会依次执行,从而达到串行(线性)的目的。其代码量并未比使用co减小,但使用的缺失原生提供的能力,而不须要封装的模块。在观察,发现代码中在使用for循环(同generator实现代码同),而非forEach等数组方法,这是由于forEach等数组方法的参数为函数,将使得async or generator失效~~网络
generator版本的资源预加载,使用封装的co模块,相对generator自己,其更接近async/await。并发
tips:async/await的实质是promise,调用async函数,返回的为promise对象,因此在init中调用autoExeImageStream以后,直接可食用.then方法。异步
如此,有没有更好的办法来实现串行(线性)呢?async
优化代码以下:异步编程
init() { if (this.auto) { this.autoExeImageStream() } } autoExeImageStream() { let seriesObj = [] this.imgs.forEach((item, index) => { if (this.ignore.indexOf(index) != -1) { return } seriesObj.push((done) => { this.loadOneSetImages(item).then(() => { done(null, index) }) }) }) async.series(seriesObj, (error, result) => { console.log('资源加载完成~', result) }) }
如上代码,能够看出init函数变得更简洁,只需实现autoExeImageStream的调用;而该函数当中调用了async.series来实现串行化。其经过回调函数done来肯定当前函数是否结束,因此在处理异步时,须要将done函数放入异步回调的结尾处,从而找到当前任务结束的节点。
预加载方法的使用:
new Preload1(true, [ [ 'http://139.198.15.201:3000/images/channel_cover_1001_1506568590_adj02.jpg', 'http://139.198.15.201:3000/images/channel_cover_1007_1506577051_timg.jpg' ], [ 'http://139.198.15.201:3000/images/channel_cover_1008_1506579805_每周带小学生共读一本书.jpg', 'http://139.198.15.201:3000/images/channel_cover_1009_1506584962_科学队长1.jpg' ], [ 'http://139.198.15.201:3000/images/channel_cover_1010_1506586126_世界童话故事.jpg' ] ])
上述三种代码的执行结果相同,其结果以下:
图一 console输出
图二 nextwork资源加载图
总结:从(图二 nextwork资源加载图)能够看出,图片是按照资源二维数组的顺序,按批次加载的。只有上一批次所有加载完毕,才可进行下一批的加载,从而实现串行的异步加载。
几个关键模块或者方法总结:
promise:
全部异步处理的基础,属于microtask~在浏览器环境下,几乎比全部的异步都要更早处理~常见的时间、网络等异步,都属于macrotask~但在nodejs环境当中,其级别低于Process.nextTick~~详情可经过事件循环相关资料获取;
async/await:
ES7的方法,其也是promise的封装;其做用为串行执行异步,在以事件循环为核心的nodejs中,给开发者提供各类便利~~同时,其返回promise对象,但只返回resolve部分,因此在调用该方法时,若是返回结果不肯定的状况下,需考虑使用try{}catch(){}捕获,避免报错;
generator:
其为一个状态机,使用next方法,控制移至下一个状态,从而控制串行(相似手动移交状态);
co:
其有两种实现方式,thunk or promise~~而它是将generator进行的封装,经过自动控制next方法,实现串行。因为async/await的支持度已经很高,可考虑使用其替代该模块。
async模块使用详情参考:nodejs之async异步编程