文档章节

any-loader JS数据加载器中间件

曾建凯
 曾建凯
发布于 2018/11/13 17:49
字数 1893
阅读 113
收藏 2

简介

any-loader 旨在为 node.js 和其他的 javascript 提供一个可定制程度较高的数据加载器中间件类库。本身并不实现任何数据加载器的实现逻辑,只界定了数据 流走向的标准接口 newLoadStrem -> setup -> beforeLoad -> doLoad -> afterLoad ,调用顺序(不可逆),以及此过程中的异常错误处理机制。

any-loader 支持并实现了以下编程特性:

  • 基于AOP设计,支持异步(Promise)。
  • 中间件形态,不干涉业务逻辑和底层实现。
  • 使用OOP进行扩展,使用继承和方法重载,来进行子类的开发,并提供丰富的方法以控制的粒度。
  • 接口基于 Promise 封装,向后兼容 async/await 语法
  • 数据流(LoadStream)部分,使用 fp 编程,数据流持有的 inputoutput 等数据,只在接口中流转,结束后即作废。 Loader 本身无状态,不持有过程数据。

码云仓库地址:https://gitee.com/janpoem/any-loader

设计初衷

在决定将 any-loader 作为独立的项目前,正忙于一个基于 React.js Web 实现的后台文件管理系统,因为前端环境和服务器环境,需要在前端集成比较多的数据接口。

  • 一般的 Ajax 拉取文件列表、单个文件、更新文件修改等。
  • 前端的 FileReader ,识别和检测用户上传文件的安全性,以及图片和视频客户端生成预览(嗯,现在这些都转移到前端实现了,没必要交给后端做了,以后有空再把这一块开源)。
  • 客户端直接上传到 CDN,没必要再从服务器走一趟了,根据文件类型,还要通知 CDN 对文件进行各种处理(如视频转码、压缩分辨率,图片生成缩略图等)。
  • 上传完毕,需要更新服务器端,记录文件信息,以及有效的 CDN 资源地址。

前端需要异步调用的地方很多,最初的想法是将功能和资源点接近形成一个加载器组,进行封装管理。可是随着开发的代码增加,就越发发现加载器组控制粒度不够细。

  1. 随着接口越来越多,应用层面、界面层面的调用代码越来越多,越来越多的结构控制,更别提在不改变调用代码的前提下,去扩展和细化加载器的中间逻辑代码,只能不断的增加应用层的代码量。
  2. 异步环调用情况更恶劣。项目里有对Ajax的请求封装,但这只适合单次Ajax(适合网站前台)请求,后台的请求,特别是文件管理系统,往往在执行一个操作,往往涉及到一个系列的异步调用环。比如上传到 CDN,要先从服务器端拿到 token ,上传完毕后,还需要将信息保存到服务器端。
  3. 基于 JS 老掉牙的事件驱动,应用层的代码会臃肿不堪,不断嵌套的事件注册,不利于后续的扩展和开发。
  4. 缺乏统一调度管理,这里说的调度,即同类接口的并发策略,是等待、取消还是延后等。嗯,是的,当更大程度的使用 React.js ,对于各种数据加载,其实隐性的存在这一个并发调度管理的需求,现有的各种工具类库,并没有在这些方面有很好的着力点,可以说完全为零。在C#和Java等静态语言有线程安全一说,可是在过去20多年的JS开发中,并没有这个概念。但随着现在前端技术发展的程度,前端的异步调度安全性,成为一个非常重要的内容(特别是Web Worker、Service Worker、大量的 Promise 环境下)。
  5. 基于 React.js 的一些特性,想将数据加载接口传递进组件内被使用,是一个比较头疼的问题。当然可以选择使用 Redux 等,Redux 开拓了一个全新的编码区间,以解决这方面的问题。但我并不是太喜欢这种动不动就打开一个新的编码空间的做法,太多框架一再用事实告诉我们,如果不解决问题本身,而为了解决某类型问题去开拓一个新的编码区间,最终那个区间只会成为一个无王法、无规范,代码质量差,问题成堆的集中地,所以还是要回到问题本质。

经过一番思索和准备,我决定将 any-loader 作为一个独立的类库来实现。any-loader 不旨在解决实际加载器的业务流程的复杂度,也不提供 Loader 的实现,更不会考虑对任何数据加载方式做封装。any-loader 只定义了一个数据加载流的接口调用顺序,并将足够多的方法和接口进行暴露,提供给子类更多细化调节和扩展的空间。同时,在不改变应用层的调用代码的前提下,随着项目的开发程度和需求细化程度,可以逐步对项目实际的 Loader 进行渐进式的升级和扩展,而不需要一再的去调整应用层的调用代码。

简单示例

class ImageLoader extends Loader {
	
	// 默认形态下,input, output 是 {}
	doLoad({input, output, errors}) {
		return new Promise((resolve, reject) => {
			const image = new Image();
            image.onload = function(ev) {
                output.image = this;
                output.width = this.width;
                output.height = this.height;
                resolve();
            };
            image.onerror = (ev) => {
                reject(new Error('图片加载失败!'));
            };
            image.src = input.url;
		});
	}
}

const loader = new ImageLoader();
loader.load({url: 'https://www.oschina.net/build/oschina/components/imgs/header/logo.svg'}).then(({output}) => {
}).catch(error => {
});

这里定义了一个图片加载器,通过 doLoad 方法的重载,来实现该加载器的具体实现。当然这个例子看起来很简单,市面上大把这样的图片加载器的类库。下来我们接着扩展。

// 我们先定义了一个远程的URL类,或者你的项目本身就有类似的设定
class RemoteURL {
	
	constructor() {
		// ....
	}
	
	toURL() {
		return '...';
	}
}

// 再定义一个远程的图片类
class RemoteImage {
	
	constructor(remoteUrl) {
        this.url = remoteUrl; // 这是一个RemoteURL的实例
        this.isLoad = false;
        this.image = null;
        this.error = null;
    }
    
    load(image) {
		this.isLoad = true;
		this.image = image;
    }
    
    error(error) {
		this.error = error;
    }
}

class ImageLoader extends Loader {
	
	// 我们将 RemoteURL 的实例,作为 LoadStream 的 input
	newInput(input) {
        return new RemoteURL(this.mergeArgs(input));
    }

    newOutput(input, output) {
		// 到这里时,input已经变为 RemoteURL 的实例
        return new RemoteImage(input);
    }
    
    // input => RemoteURL, output => RemoteImage
    doLoad({input, output, errors}) {
        return new Promise((resolve, reject) => {
            const image = new Image();
            image.onload = function(ev) {
                output.load(this);
                resolve();
            };
            image.onerror = (ev) => {
            	output.error(new Error('图片加载失败!'));
            	reject(output.error);
            };
            image.src = input.toURL();
        });
    }
}

// 调用代码
const loader = new ImageLoader();
loader.load({url: 'https://www.oschina.net/build/oschina/components/imgs/header/logo.svg'}).then(({output}) => {
}).catch(error => {
});

第二个例子中,我们增加了两个中间类,以对ImageLoader 的输入、输出,进行更细的控制。同时,为ImageLoader重载了两个方法,以将输入、输出的实例绑定到ImageLoader 标准流程中去。在应用层调用的代码不变的前提下,通过增加中间层的代码,实现了对Loader更多的控制。

更多例子,后续更新

版本说明

现阶段,不考虑基于类库层面解决并发策略的问题,而在具体的项目里实现的 子类Loader 去简单的管理。

未完,待续。

© 著作权归作者所有

曾建凯

曾建凯

粉丝 337
博文 66
码字总数 104794
作品 0
广州
技术主管
私信 提问
demo11 webpack处理css

1.关于模块 在 webpack 中,所有类型的文件都是模块,比如 js、css、图片、字体、json(可以说是万物皆模块)。 但是,在普通的 js 代码中,我们直接 import (或require) 一张图片或css是会报...

SimpleXD
05/25
0
0
webpack图片路径与打包问题

Webpack是一个模块打包工具。平时我们开发中为了提高开发效率会使用一些开发框架React、Vue等、拓展性语言Typescript、Flow等、CSS预处理器Scss/Sass/Less/Stylus等以及一些新语法特性ES6等,...

我你日哥
02/15
0
0
avalonJS-源码阅读(前)

avalon模块加载 avalon自己实现了一套可被替换的模块加载系统(AMD loader)。具体什么是AMD loader可参看doJo官方博客关于AMD loader的翻译讲解,看完之后,再继续往下看,会比较清楚些。 ...

lost_o0
2014/05/02
800
0
Vue开发入门看这篇文章就够了

摘要: 很多值得了解的细节。 原文:Vue开发看这篇文章就够了 作者:Random Fundebug经授权转载,版权归原作者所有。 介绍 Vue 中文网 Vue github Vue.js 是一套构建用户界面(UI)的渐进式Jav...

Fundebug
06/03
30
0
[翻译]阮一峰webpack教程

翻译它主要是用于学习Webpack,原地址为github.com/ruanyf/webp… 安装使用指南 首先,全局安装Webpack和webpack-dev-server 然后克隆clone阮一峰的仓库 安装依赖 现在开始进入demo*目录并且...

爱吃叉烧
2018/07/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Rust:最小化窗口后 CPU占用率高 (winit,glutin,imgui-rust)

最近试着用 imgui-rust 绘制界面,发现窗口最小化后CPU占用会增大。 查询的资料如下: https://github.com/rust-windowing/winit/issues/783 https://github.com/ocornut/imgui/issues/1151 ...

reter
17分钟前
4
0
cloud-zuul路由网关

九、zuul路由网关 概述 1.1 能干嘛 路由、过滤 路由基本配置 路由访问映射规则 十、springCloud config分布式配置中心

榴莲黑芝麻糊
17分钟前
5
0
Circuit Breaker模式

Circuit Breaker模式会处理一些需要一定时间来重连远程服务和远端资源的错误。该模式可以提高一个应用的稳定性和弹性。 问题 在类似于云的分布式环境中,当一个应用需要执行一些访问远程资源...

mskk
30分钟前
7
0
写论文之前的准备都有哪些?干货来了!

原文链接:https://www.lwfdy.com/archives/144.html 之前跟大家谈了许多有关于初稿修改以及写作事项需要注意的问题,那么今天我们来说一说,在写之前,我们需要做哪些准备呢,为了做到下笔如...

辅导员
36分钟前
6
0
idea快捷键

Alt + Enter 引入类 Ctrl + O 查看我们继承的类或者接口中的方法,以及我们要实现的方法 Ctrl + Alt + b 查看接口实现类中方法(就是我们使用接口编程时,在调用实现类方法处直接Ctrl+鼠标左...

行者终成事
44分钟前
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部