ES6(四)用Promise封装一下IndexedDB
indexedDB
IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据,它可以被网页脚本创建和操作。
IndexedDB 允许储存大量数据,提供查找接口,还能建立索引,这些都是 LocalStorage 所不具备的。
就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。
其他的介绍就不搬运了,大家可以自行百度,后面有参考资料。
需求
我想更好的实现文档驱动的想法,发现需要实现前端存储的功能,于是打算采用 IndexedDB 来实现前端存储的功能。但是看了一下其操作方式比较繁琐,所以打算封装一下。
官网给了几个第三方的封装库,我也点过去看了看,结果没看懂。想了想还是自己动手丰衣足食吧。
关于重复制造轮子的想法:
- 首先要有制造轮子能能力。
- 自己造的轮子,操控性更好。
功能设计
按照官网的功能介绍,把功能整理了一下:
如图:
就是建库、增删改查那一套。看到有些第三方的封装库,可以实现支持sql语句方式的查询,真的很厉害。目前没有这种需求,好吧,能力有限实现不了。
总之,先满足自己的需求,以后在慢慢改进。
代码实现
还是简单粗暴,直接上代码吧,基础知识的介绍,网上有很多了,可以看后面的参考资料。官网介绍的也比较详细,还有中文版的。
配置文件
nf-indexedDB.config
const config = {
dbName: 'dbTest',
ver: 1,
debug: true,
objectStores: [ // 建库依据
{
objectStoreName: 'blog',
index: [ // 索引 , unique 是否可以重复
{ name: 'groupId', unique: false }
]
}
],
objects: { // 初始化数据
blog: [
{
id: 1,
groupId: 1,
title: '这是一个博客',
addTime: '2020-10-15',
introduction: '这是博客简介',
concent: '这是博客的详细内容<br>第二行',
viewCount: 1,
agreeCount: 1
},
{
id: 2,
groupId: 2,
title: '这是两个博客',
addTime: '2020-10-15',
introduction: '这是博客简介',
concent: '这是博客的详细内容<br>第二行',
viewCount: 10,
agreeCount: 10
}
]
}
}
export default config
dbName
指定数据库名称ver
指定数据库版本debug
指定是否要打印状态objectStores
对象仓库的描述,库名、索引等。objects
初始化数据,如果建库后需要添加默认数据的话,可以在这里设置。
这里的设置不太完善,有些小问题现在还没想好解决方法。以后想好了再改。
内部成员
/**
* IndexedDB 数据库对象
* 判断浏览器是否支持
* */
const myIndexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB
if (!myIndexedDB) {
console.log('你的浏览器不支持IndexedDB')
}
let _db // 内部保存的 indexed 数据库 的实例
/**
* 把vue的ref、reactive转换成原始对象
*/
const _vueToObject = (vueObject) => {
let _object = vueObject
// 针对Vue3做的类型判断
if (Vue.isRef(_object)) {
// 如果是 vue 的 ref 类型,替换成 ref.value
_object = _object.value
}
if (Vue.isReactive(_object)) {
// 如果是 vue 的 reactive 类型,那么获取原型,否则会报错
_object = Vue.toRaw(_object)
}
return _object
}
myIndexedDB
兼容浏览器的写法,适应不同的浏览器。_db 内部的 IDBOpenDBRequest 用于检查是否打开数据库,以及数据库的相关操作。
_vueToObject
这是一个兼容Vue的对象转换函数。vue的reactive直接存入的话会报错,需要获取原型才能存入,我又不想每次保存的时候都多一步操作,所以就写了这个转换函数。
如果非vue3环境,可以直接返回参数,不影响其他功能。
建立对象库以及打开数据库
// ======== 数据库操作 ================
/**
* 打开 indexedDB 数据库。
* dbName:数据库名称;
* version:数据库版本。
* 可以不传值。
*/
const dbOpen = (dbName, version) => {
// 创建数据库,并且打开
const name = config.dbName || dbName
const ver = config.ver || version
const dbRequest = myIndexedDB.open(name, ver)
// 记录数据库版本是否变更
let isChange = false
/* 该域中的数据库myIndex */
if (config.debug) {
console.log('dbRequest - 打开indexedDb数据库:', dbRequest)
}
// 打开数据库的 promise
const dbPromise = new Promise((resolve, reject) => {
// 数据库打开成功的回调
dbRequest.onsuccess = (event) => {
// _db = event.target.result
// 数据库成功打开后,记录数据库对象
_db = dbRequest.result
if (isChange) { // 如果变更,则设置初始数据
setup().then(() => {
resolve(_db)
})
} else {
resolve(_db)
}
}
dbRequest.onerror = (event) => {
reject(event) // 返回参数
}
})
// 创建表
// 第一次打开成功后或者版本有变化自动执行以下事件,一般用于初始化数据库。
dbRequest.onupgradeneeded = (event) => {
isChange = true
_db = event.target.result /* 数据库对象 */
// 建立对象表
for (let i = 0; i < config.objectStores.length; i++) {
const object = config.objectStores[i]
// 验证有没有,没有的话建立一个对象表
if (!_db.objectStoreNames.contains(object.objectStoreName)) {
const objectStore = _db.createObjectStore(object.objectStoreName, { keyPath: 'id' }) /* 创建person仓库(表) 主键 */
// objectStore = _db.createObjectStore('person',{autoIncrement:true});/*自动创建主键*/
// 建立索引
for (let i = 0; i < object.index.length; i++) {
const index = object.index[i]
objectStore.createIndex(index.name, index.name, { unique: index.unique })
}
if (config.debug) {
console.log('onupgradeneeded - 建立了一个新的对象仓库:', objectStore)
}
}
}
}
// 返回 Promise 实例 —— 打开Indexed库
return dbPromise
}
这段代码有点长,因为有两个功能,一个是打开数据库,一个是创建数据库。
indexedDB 的逻辑是这样的,在open数据库的时候判断本地有没有数据库,如果没有数据库则触发 onupgradeneeded 事件,创建数据库,然后打开数据库。
如果有数据库的话,判断版本号,如果高于本地数据库,那么也会触发 onupgradeneeded 事件。所以open和 onupgradeneeded 就联系在了一起。
初始化对象
/**
* 设置初始数据
*/
const setup = () => {
// 定义一个 Promise 的实例
const objectPromise = new Promise((resolve, reject) => {
const arrStore = []
// 遍历,获取表名集合,便于打开事务
for (const key in config.objects) {
arrStore.push(key)
}
const tranRequest = _db.transaction(arrStore, 'readwrite')
// 遍历,添加数据(对象)
for (const key in config.objects) {
const objectArror = config.objects[key]
const store = tranRequest.objectStore(key)
// 清空数据
store.clear().onsuccess = (event) => {
// 遍历添加数据
for (let i = 0; i < objectArror.length; i++) {
store
.add(objectArror[i])
.onsuccess = (event) => {
if (config.debug) {
console.log(`添加成功!key:${key}-i:${i}`)
}
}
}
}
}
// 遍历后统一返回
tranRequest.oncomplete = (event) => {
// tranRequest.commit()
if (config.debug) {
console.log('setup - oncomplete')
}
resolve()
}
tranRequest.onerror = (event) => {
reject(event)
}
})
return objectPromise
}
有的时候需要在建库之后设置一些初始化的数据,于是设计了这个函数。
setup会依据 nf-indexedDB.config 里的配置,把默认对象添加到数据库里面。
添加对象
基础的增删改查系列,不管是数据库还是对象库,都躲不开。
// ======== 增删改操作 ===================================
/**
* 添加对象。
* storeName:对象仓库名;
* object:要添加的对象
*/
const addObject = (storeName, object) => {
const _object = _vueToObject(object)
// 定义一个 Promise 的实例
const objectPromise = new Promise((resolve, reject) => {
// 定义个函数,便于调用
const _addObject = () => {
const tranRequest = _db.transaction(storeName, 'readwrite')
tranRequest
.objectStore(storeName) // 获取store
.add(_object) // 添加对象
.onsuccess = (event) => { // 成功后的回调
resolve(event.target.result) // 返回对象的ID
}
tranRequest.onerror = (event) => {
reject(event)
}
}
// 判断数据库是否打开
if (typeof _db === 'undefined') {
dbOpen().then(() => {
_addObject()
})
} else {
_addObject()
}
})
return objectPromise
}
这么长的代码,只是实现了把一个对象填到数据库里的操作,可见原本的操作是多么的繁琐。
好吧,不开玩笑了,其实原本的想法是这样的,想要添加对象要这么写:
dbOpen().then(() =>{
addObject('blog',{
id: 3,
groupId: 1,
title: '这是三个博客',
addTime: '2020-10-15',
introduction: '这是博客简介',
concent: '这是博客的详细内容<br>第二行',
viewCount: 1,
agreeCount: 1
})
})
就是说,每次操作的时候先开库,然后才能进行操作,但是想想这么做是不是有点麻烦?
能不能不管开不开库的,直接开鲁呢?
于是内部实现代码就变得复杂了一点。
修改对象
/**
* 修改对象。
* storeName:对象仓库名;
* object:要修改的对象
*/
const updateObject = (storeName, object) => {
const _object = _vueToObject(object)
// 定义一个 Promise 的实例
const objectPromise = new Promise((resolve, reject) => {
// 定义个函数,便于调用
const _updateObject = () => {
const tranRequest = _db.transaction(storeName, 'readwrite')
// 按照id获取对象
tranRequest
.objectStore(storeName) // 获取store
.get(_object.id) // 获取对象
.onsuccess = (event) => { // 成功后的回调
// 从仓库里提取对象,把修改值合并到对象里面。
const newObject = { ...event.target.result, ..._object }
// 修改数据
tranRequest
.objectStore(storeName) // 获取store
.put(newObject) // 修改对象
.onsuccess = (event) => { // 成功后的回调
if (config.debug) {
console.log('updateObject -- onsuccess- event:', event)
}
resolve(event.target.result)
}
}
tranRequest.onerror = (event) => {
reject(event)
}
}
// 判断数据库是否打开
if (typeof _db === 'undefined') {
dbOpen().then(() => {
_updateObject()
})
} else {
_updateObject()
}
})
return objectPromise
}
修改对象,是新的对象覆盖掉原来的对象,一开始是想直接put,但是后来实践的时候发现,可能修改的时候只是修改其中的一部分属性,而不是全部属性,那么直接覆盖的话,岂不是造成参数不全的事情了吗?
于是只好先把对象拿出来,然后和新对象合并一下,然后再put回去,于是代码就又变得这么长了。
删除对象
/**
* 依据id删除对象。
* storeName:对象仓库名;
* id:要删除的对象的key值,注意类型要准确。
*/
const deleteObject = (storeName, id) => {
// 定义一个 Promise 的实例
const objectPromise = new Promise((resolve, reject) => {
// 定义个函数,便于调用
const _deleteObject = () => {
const tranRequest = _db.transaction(storeName, 'readwrite')
tranRequest
.objectStore(storeName) // 获取store
.delete(id) // 删除一个对象
.onsuccess = (event) => { // 成功后的回调
resolve(event.target.result)
}
tranRequest.onerror = (event) => {
reject(event)
}
}
// 判断数据库是否打开
if (typeof _db === 'undefined') {
dbOpen().then(() => {
_deleteObject()
})
} else {
_deleteObject()
}
})
return objectPromise
}
其实吧删除对象,一个 delete 就可以了,但是还是要先判断一下是否打开数据库,于是代码还是短不了。
清空仓库里的对象
/**
* 清空store里的所有对象。
* storeName:对象仓库名;
*/
const clearStore = (storeName) => {
// 定义一个 Promise 的实例
const objectPromise = new Promise((resolve, reject) => {
// 定义个函数,便于调用
const _clearStore = () => {
const tranRequest = _db.transaction(storeName, 'readwrite')
tranRequest
.objectStore(storeName) // 获取store
.clear() // 清空对象仓库里的对象
.onsuccess = (event) => { // 成功后的回调
resolve(event)
}
tranRequest.onerror = (event) => {
reject(event)
}
}
// 判断数据库是否打开
if (typeof _db === 'undefined') {
dbOpen().then(() => {
_clearStore()
})
} else {
_clearStore()
}
})
return objectPromise
}
- clear()
清空指定对象仓库里的所有对象,请谨慎操作。
删除对象仓库
/**
* 删除整个store。
* storeName:对象仓库名;
*/
const deleteStore = (storeName) => {
// 定义一个 Promise 的实例
const objectPromise = new Promise((resolve, reject) => {
// 定义个函数,便于调用
const _deleteStore = () => {
const tranRequest = _db.transaction(storeName, 'readwrite')
tranRequest
.objectStore(storeName) // 获取store
.delete() // 清空对象仓库里的对象
.onsuccess = (event) => { // 成功后的回调
resolve(event)
}
tranRequest.onerror = (event) => {
reject(event) // 失败后的回调
}
}
// 判断数据库是否打开
if (typeof _db === 'undefined') {
dbOpen().then(() => {
_deleteStore()
})
} else {
_deleteStore()
}
})
return objectPromise
}
这个就更厉害了,可以把对象仓库给删掉。更要谨慎。
删除数据库
/**
* 删除数据库。
* dbName:数据库名;
*/
const deleteDB = (dbName) => {
// 定义一个 Promise 的实例
const objectPromise = new Promise((resolve, reject) => {
// 删掉整个数据库
myIndexedDB.deleteDatabase(dbName).onsuccess = (event) => {
resolve(event)
}
})
return objectPromise
}
能建立数据库,那么就应该能删除数据库,这个就是。
这个就非常简单了,不用判断是否打开数据库,直接删除就好。
不过前端数据库应该具备这样的功能:整个库删掉后,可以自动恢复状态才行。
按主键获取对象,或者获取全部
/**
* 获取对象。
* storeName:对象仓库名;
* id:要获取的对象的key值,注意类型要准确,只能取一个。
* 如果不设置id,会返回store里的全部对象
*/
const getObject = (storeName, id) => {
const objectPromise = new Promise((resolve, reject) => {
const _getObject = () => {
const tranRequest = _db.transaction(storeName, 'readonly')
const store = tranRequest.objectStore(storeName) // 获取store
let dbRequest
// 判断是获取一个,还是获取全部
if (typeof id === 'undefined') {
dbRequest = store.getAll()
} else {
dbRequest = store.get(id)
}
dbRequest.onsuccess = (event) => { // 成功后的回调
if (config.debug) {
console.log('getObject -- onsuccess- event:', id, event)
}
resolve(event.target.result) // 返回对象
}
tranRequest.onerror = (event) => {
reject(event)
}
}
// 判断数据库是否打开
if (typeof _db === 'undefined') {
dbOpen().then(() => {
_getObject()
})
} else {
_getObject()
}
})
return objectPromise
}
这里有两个功能
- 依据ID获取对应的对象
- 获取对象仓库里的所有对象
不想取两个函数名,于是就依据参数来区分了,传递ID就获取ID的对象,没有传递ID就返回全部。
查询对象仓库
/**
* 依据 索引+游标,获取对象,可以获取多条。
* storeName:对象仓库名。
* page:{
* start:开始,
* count:数量,
* description:'next'
* // next 升序
* // prev 降序
* // nextunique 升序,只取一
* // prevunique 降序,只取一
* }
* findInfo = {
* indexName: 'groupId',
* indexKind: '=', // '>','>=','<','<=','between',
* indexValue: 1,
* betweenInfo: {
* v1:1,
* v2:2,
* v1isClose:true,
* v2isClose:true,
* },
* where:(object) => {
* reutrn true/false
* }
* }
*/
const findObject = (storeName, findInfo = {}, page = {}) => {
const _start = page.start || 0
const _count = page.count || 0
const _end = _start + _count
const _description = page.description || 'prev' // 默认倒序
// 查询条件,按照主键或者索引查询
let keyRange = null
if (typeof findInfo.indexName !== "undefined") {
if (typeof findInfo.indexKind !== "undefined") {
const id = findInfo.indexValue
const dicRange = {
"=":IDBKeyRange.only(id),
">":IDBKeyRange.lowerBound(id, true),
">=":IDBKeyRange.lowerBound(id),
"<":IDBKeyRange.upperBound(id, true),
"<=":IDBKeyRange.upperBound(id)
}
switch (findInfo.indexKind) {
case '=':
case '>':
case '>=':
case '<':
case '<=':
keyRange = dicRange[findInfo.indexKind]
break
case 'between':
const betweenInfo = findInfo.betweenInfo
keyRange = IDBKeyRange.bound(betweenInfo.v1,betweenInfo.v2,betweenInfo.v1isClose,betweenInfo.v2isClose)
break
}
}
}
console.log('findObject - keyRange', keyRange)
const objectPromise = new Promise((resolve, reject) => {
// 定义个函数,便于调用
const _findObjectByIndex = () => {
const dataList = []
let cursorIndex = 0
const tranRequest = _db.transaction(storeName, 'readonly')
const store = tranRequest.objectStore(storeName)
let cursorRequest
// 判断是否索引查询
if (typeof findInfo.indexName === "undefined") {
cursorRequest = store.openCursor(keyRange, _description)
} else {
cursorRequest = store
.index(findInfo.indexName)
.openCursor(keyRange, _description)
}
cursorRequest.onsuccess = (event) => {
const cursor = event.target.result
if (cursor) {
if (_end === 0 || (cursorIndex >= _start && cursorIndex < _end)) {
// 判断钩子函数
if (typeof findInfo.where === 'function') {
if (findInfo.where(cursor.value, cursorIndex)) {
dataList.push(cursor.value)
cursorIndex++
}
} else { // 没有设置查询条件
dataList.push(cursor.value)
cursorIndex++
}
}
cursor.continue()
}
// tranRequest.commit()
}
tranRequest.oncomplete = (event) => {
if (config.debug) {
console.log('findObjectByIndex - dataList', dataList)
}
resolve(dataList)
}
tranRequest.onerror = (event) => {
console.log('findObjectByIndex - onerror', event)
reject(event)
}
}
// 判断数据库是否打开
if (typeof _db === 'undefined') {
dbOpen().then(() => {
_findObjectByIndex()
})
} else {
_findObjectByIndex()
}
})
return objectPromise
}
打开指定的对象仓库,然后判断是否设置了索引查询,没有的话打开仓库的游标,如果设置了,打开索引的游标。
可以用钩子实现其他属性的查询。
可以分页获取数据,方法类似于mySQL的 limit。
功能测试
封装完毕,要写个测试代码来跑一跑,否则怎么知道到底好不好用呢。
于是写了一个比较简单的测试代码。
建立对象库
dbOpen().then(() =>{
// 建表初始化之后,获取全部对象
getAll()
})
- dbOpen
打开数据库,同时判断是否需要建立数据库,如果需要的话,会根据配置信息自动建立数据库
然后我们按F12,打开Application标签,可以找到我们建立的数据库,如图:
我们可以看一下索引的情况,如图:
添加对象
addObject('blog',{
id: new Date().valueOf(),
groupId: 1,
title: '这是三个博客',
addTime: '2020-10-15',
introduction: '这是博客简介',
concent: '这是博客的详细内容<br>第二行',
viewCount: 1,
agreeCount: 1
}).then((data) => {
re.value = data
getAll()
})
仓库名
第一个参数是对象仓库的名称,目前暂时采用字符串的形式。对象
第二个参数是要添加的对象,其属性必须有主键和索引,其他随意。返回值
成功后会返回对象ID
点右键可以刷新数据,如图:
更新后的数据,如图:
修改对象
updateObject('blog',blog).then((data) => {
re.value = data
getAll()
})
仓库名
第一个参数是对象仓库的名称,目前暂时采用字符串的形式。对象
第二个参数是要修改的对象,属性可以不全。返回值
成功后会返回对象ID
删除对象
deleteObject('blog',id).then((data) => {
re.value = data
getAll()
})
仓库名
第一个参数是对象仓库的名称,目前暂时采用字符串的形式。对象
第二个参数是要删除的对象的ID。返回值
成功后会返回对象ID
清空仓库里的对象
clearStore('blog').then((data) => {
re.value = data
getAll()
})
仓库名
第一个参数是对象仓库的名称,目前暂时采用字符串的形式。返回值
成功后会返回对象ID
删除对象仓库
deleteStore('blog').then((data) => {
re.value = data
getAll()
})
仓库名
第一个参数是对象仓库的名称,目前暂时采用字符串的形式。返回值
成功后会返回对象ID
删除数据库
deleteDB('dbTest').then((data) => {
re.value = data
getAll()
})
- 数据库名称
第一个参数是数据库的名称
查询功能
// 查询条件
const findInfo = {
indexName: 'groupId',
indexKind: '=', // '>','>=','<','<=','between',
indexValue: 1,
betweenInfo: {
v1:1,
v2:2,
v1isClose:true,
v2isClose:true,
},
where: (object) => {
if (findKey.value == '') return true
let re = false
if (object.title.indexOf(findKey.value) >= 0) {
re = true
}
if (object.introduction.indexOf(findKey.value) >= 0) {
re = true
}
if (object.concent.indexOf(findKey.value) >= 0) {
re = true
}
return re
}
}
const find = () => {
findObject('blog', findInfo).then((data) => {
findRe.value = data
})
}
findInfo
查询信息的对象,把需要查询的信息都放在这里indexName
索引名称,可以不设置。indexKind
索引属性的查询方式,如果设置indexName,则必须设置。indexValue
索引字段的查询值betweenInfo
如果 indexKind = 'between' 的话,需要设置。v1
开始值v2
结束值v1isClose
是否闭合区间v2isClose
是否闭合区间where
钩子函数,可以不设置。
内部打开游标后,会把对象返回来,然后我们就可以在这里进行各种条件判断。
全部代码就不贴了,感兴趣的话可以去GitHub看。
贴一个折叠后的效果图吧:
就是先把相关的功能和在一起,写一个操作类,然后在setup里面应用这个类就可以了,然后写点代码把各个类关联起来即可。
这样代码好维护多了。
小结
功能不是很完善,目前是自己够用的程度。
本来想用纯js来写个使用方式的,但是发现还是用vue写着方便,于是测试代码就变成了vue的形式。
源码
https://github.com/naturefwvue/nf-vue-cnd/tree/main/cnd/LocalStore/IndexedDB
在线演示
https://naturefwvue.github.io/nf-vue-cnd/cnd/LocalStore/IndexedDB/
参考资料
官网:https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API
阮一峰的网络日志:http://www.ruanyifeng.com/blog/2018/07/indexeddb.html
谦行: https://www.cnblogs.com/dolphinX/p/3416889.html
ES6(四)用Promise封装一下IndexedDB的更多相关文章
- 微信小程序ES6方法Promise封装接口
为何要封装接口? 有小程序开发的经验者,相信对微信API Request很熟悉了.对接接口时,有大部分的开发者都是直接调用request方法,去请求后台接口并渲染数据.诚然,直接使用api发起请求对接 ...
- 【前端开发】】ES6属性promise封装js动画
如下是我写的demo源码: 可以直接复制用浏览器打开看到效果哦: <!DOCTYPE html> <html> <head> <meta charset=&q ...
- es6学习笔记--promise对象
Promise对象是为了简化异步编程.解决回调地狱情况 Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果.从语法上说,Promise 是一个对象,从它可 ...
- React Native 网络请求封装:使用Promise封装fetch请求
最近公司使用React作为前端框架,使用了异步请求访问,这里做下总结: React Native中虽然也内置了XMLHttpRequest 网络请求API(也就是俗称的ajax),但XMLHttpRe ...
- es6 -- 透彻掌握Promise的使用,读这篇就够了
Promise的重要性我认为我没有必要多讲,概括起来说就是必须得掌握,而且还要掌握透彻.这篇文章的开头,主要跟大家分析一下,为什么会有Promise出现. 在实际的使用当中,有非常多的应用场景我们不能 ...
- jsonp的原理介绍及Promise封装
什么叫jsonp? jsonp是json with padding(填充式json或参数式json)的简写,是通过ajax请求跨域接口,获取数据的新实现方式 jsonp的实现原理: 动态创建scrip ...
- es6中的promise对象
Promise是异步里面的一种解决方案,解决了回调嵌套的问题,es6将其进行了语言标准,同意了用法,提供了`promise`对象, promise对象有三种状态:pending(进行中) .Resol ...
- ES6中的Promise用法
Node的产生,大大推动了Javascript这门语言在服务端的发展,使得前端人员可以以很低的门槛转向后端开发. 当然,这并不代表迸发成了全栈.全栈的技能很集中,绝不仅仅是前端会写一些HTML和一些交 ...
- vue中利用Promise封装jsonp并调取数据
Promise就是一个给一步操作提供的容器,在这个容器里,有两个阶段无法改变的阶段,第一个阶段就是Pending(进行),第二个阶段就是结果阶段,包含Fulfilled(成功).Rejected(失败 ...
随机推荐
- 如何将项目推到github上面
1.先查看是否安装git. 2.如果没有安装git ,下载之后别忘了配置环境变量.(右击此电脑 --属性--高级系统设置--环境变量--系统变量中的path) 3.推代码 查看状态(可查可不查) gi ...
- 一个上传图片,预览图片的小demo
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- JVM-Class文件的结构
Class类文件的结构 Class文件是一株以8个字节为单位的二进制流.各个数据项目严格按照顺序紧凑的排列在文件之中,中间没有任何的分隔符,当遇到占用的空间大于8个字节时,会按照高位在前的方式进行分割 ...
- 如何构建一个多人(.io) Web 游戏,第 1 部分
原文:How to Build a Multiplayer (.io) Web Game, Part 1 GitHub: https://github.com/vzhou842/example-.io ...
- 虚拟机Linux安装Oracle容器并实现局域网其他主机访问查询
该文涉及Docker下Oracle容器的安装,主机端口的设置实现局域网内终端均能连接上Oracle数据库,图解如下: 一.关于Docker安装oracle容器可以参考下面博文: https://blo ...
- 【UML】基本介绍与类图(依赖、泛化、实现、关联、聚合、组合关系)
文章目录 UML基本介绍 UML图 UML类图 类图-依赖关系(Dependence) 类图-泛化关系(generalization) 类图-实现关系(Implementation) 类图-关联关系( ...
- 【Linux】快速创建文件的命令方法
[root@centos7 dir1]# ll total 0 -rw-r--r-- 1 root root 0 Aug 15 02:39 file1 -rw-r--r-- 1 root root 0 ...
- 【Linux】Linux下如何分区及如何格式化
环境:CentOS7.1 磁盘大小是1.8T 将磁盘/dev/sda分一个分区,分区类型为xfs fdisk /dev/sda n --创建新分区 p --创建分区类型为主分区 1 --主分 ...
- kubernets之机理概览
一 了解kubernets的运行机理 1.1 了解架构 众所周知,kubernets的组成由2个部分组成 kubernets 平面 node节点 (工作节点) 控制平面的组成 etcd 分布 ...
- ctfhub技能树—文件上传—前端验证
打开靶机 查看页面信息 尝试直接上传一句话木马 提示不允许上传 查看源码 发现仅允许上传.jpg,.png,.gif三种格式的文件 上传shell.jpg并使用burpsuite抓取数据包 添加完成后 ...