之前写过一个分批预加载资源的插件,其实质便是串行执行异步,使用的方法是generator + promise ~~

前几天写了一个爬虫,抓取页面的n个页面的音频资源,其也是串行执行异步,但是在使用的async/await + promise,这里对两个方法做一下对比,会发现async/await将使得代码更为简洁。

预加载的思路如下:

a、线性(串行)的控制每批次的资源加载,完成一批,再装载另一批的处理;(按数组顺序实现每批次的加载)

b、实现单批资源的异步下载;(单批次内的资源异步下载,无需顺序)

c、实现单个资源的下载。

generator实现

代码如下:

/*
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的改造过程中,这两个部分不需要调整。

async/await

代码的简单实现如下:

	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.series模块

优化代码如下:

	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.series:async模块(并非ES7中所说的async/await),其作用在于流程控制,相应方法如下:

    • series(tasks,[callback]):串行执行;
    • waterfall(tasks,[callback]):瀑布流;其与series类似,不同之处在于参数的传递;
    • parallel(tasks,[callback]):是并行执行多个函数,每个函数都是立即执行,不需要等待其它函数先执行;
    • parallelLimit(tasks, limit, [callback]) :与parallel类似,但可限制并发的函数数量;
    • whilst(test,fn,[callback]):相当于while循环,fn函数里不管是同步还是异步都会执行完上一次循环才会执行下一次循环,对异步循环很有帮助;
    • auto(tasks,[callback]):可串行、可并行~

    async模块使用详情参考:nodejs之async异步编程

async/await 与 generator、co 的对比的更多相关文章

  1. Generator与async/await与Generator的模拟

    Generator 函数有多种理解角度.语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态. 执行 Generator 函数会返回一个遍历器对象,也就是说,Gener ...

  2. Promise与async/await与Generator

    Promise是什么: Promise是异步微任务(process.nextTick.Promise.then() catch() finally()等),用于解决异步多层嵌套回调的问题(回调地狱-- ...

  3. [每日一题]面试官问:Async/Await 如何通过同步的方式实现异步?

    关注「松宝写代码」,精选好文,每日一题 ​时间永远是自己的 每分每秒也都是为自己的将来铺垫和增值 作者:saucxs | songEagle 一.前言 2020.12.23 日刚立的 flag,每日一 ...

  4. Promise, Generator, async/await的渐进理解

    作为前端开发者的伙伴们,肯定对Promise,Generator,async/await非常熟悉不过了.Promise绝对是烂记于心,而async/await却让使大伙们感觉到爽(原来异步可以这么简单 ...

  5. JS异步编程 (2) - Promise、Generator、async/await

    JS异步编程 (2) - Promise.Generator.async/await 上篇文章我们讲了下JS异步编程的相关知识,比如什么是异步,为什么要使用异步编程以及在浏览器中JS如何实现异步的.最 ...

  6. ES6入门十一:Generator生成器、async+await、Promisify

    生成器的基本使用 生成器 + Promise async+await Promise化之Promisify工具方法 一.生成器的基本使用 在介绍生成器的使用之前,可以简单理解生成器实质上生成的就是一个 ...

  7. decorator, async/await, generator

    ////////////decorator////////// function aopFunc (target, key, descriptor) { console.log('aopFunc') ...

  8. Callback, Promise和Async/Await的对比

    Callback, Promise和Async/Await的对比 Callback Hell getData1(function (data1) { console.log('我得到data1了') ...

  9. Promise、Generator,Async/await

    我们知道JavaScript是单线程语言,如果没有异步编程非得卡死. 以前,异步编程的方法有下面四种 回调函数 事件监听 发布/订阅 Promise对象 现在据说异步编程终极解决方案是——async/ ...

随机推荐

  1. 美团基于STORM的应用

    https://my.oschina.net/dolphinboy/blog/2933843

  2. [ 9.9 ]CF每日一题系列—— 259A黑白棋盘检查问题

    http://codeforces.com/problemset/problem/259/A PS9.8日做了但是忘了发博客,所以坚持3天了呦~ 终于读懂了这个题了,心累 Describe: 给你8 ...

  3. hdu4048

    题意:给定m个数,还有n,n表示有一个长度为n的环,现在要求从M个数中选出若干个数,要求选出的数最大公约数为1,填充在n个位置中,选出的数可以重复,求多少种种方案.旋转当成一样的 . 思路:假设现在选 ...

  4. MFC中处理UI界面时的注意点

    最近开发时,在处理界面上遇到了下面的问题: 上位机与下位机通信时,如果出现超时,弹出MessageBox提示的情况下,更新界面上的CStatic控件会出现重影. 经过调查发现 原因是由于在UI线程中处 ...

  5. 2.mongodb可视化工具

    (1)去robomongo官方网址下载,官网地址(https://robomongo.org/)选择相应的版本. (2) tar -xzf robo3t-1.2.1-linux-x86_64-3e50 ...

  6. Android-Java-类与对象的关系

    类class 例如:class Student {},很多人把class Student {}称为对象或实体,其实这样并不合理,应该称为描述实体/描述对象: 因为被称为对象或实体的是,new Stud ...

  7. collaborative filtering协同过滤

    每次我想看电影的时候,都会去问我的朋友,小健.一般他推荐的电影,我都比较喜欢.显然不是所有人都有小健这样的能力.因为我碰巧和小健有类似的品味. 这个生活中的经验,实际上有着广泛的用途. 当系统需要为某 ...

  8. 利用CPaintDC::IntersectClipRect将绘图限制在局部区域

    问题背景:画带坐标的图,例如 画里面那条曲线的时候,希望将绘图区域局限在坐标范围内,范围外的就自动屏蔽掉. 两个方案,一是用CPaintDC的SelectClipRgn函数,感觉略麻烦.另一个函数,就 ...

  9. winform执行程序报错:已停止工作,windows正在检查该问题的解决方案

    每次运行程序时都会弹出错误框:winform已停止工作,windows正在检查该问题的解决方案 事件查看器错位信息: 错误应用程序名称: TMS_winform.exe,版本: 1.0.0.0,时间戳 ...

  10. 【npm】利用npm安装/删除/发布/更新/撤销发布包

      什么是npm? npm是javascript的包管理工具,是前端模块化下的一个标志性产物 简单地地说,就是通过npm下载模块,复用已有的代码,提高工作效率   1.从社区的角度:把针对某一特定问题 ...