一、背景


1、Node.js 异步控制

在之前写的 callback vs async.js vs promise vs async / await 里,我介绍了 ES6 的 promise 和 ES7 的 async / await 的基本用法。

可以肯定的是,node.js 的异步控制(asynchronous JavaScript),promise 就是未来的主流,诸如 async.js 等非 promise 库( async.js 基于 callback )终将被淘汰,而基于 promise 的第三方库(Q、when、WinJS、RSVP.js)也会被 async / await 写法取代。

延伸阅读:知乎 - nodejs异步控制「co、async、Q 、『es6原生promise』、then.js、bluebird」有何优缺点?最爱哪个?哪个简单?

2、已经有 ES6 Promise + async / await 了,为什么还要用 bluebird ?

但目前基于 async / await 的 promise 写法还不是很强大。这里可以考虑用 bluebird,它是一个第三方的 Promise 库,比 async / await 更早诞生,但是完全兼容,因为他们都是基于 Promises/A+ 的标准(下文会介绍)。

很多第三方的 promise 库都是兼容 ES6 promise 的,比如 Q 。

二、Promise 进阶


1、Promise 前世今生

(1)定义

They describe an object that acts as a proxy for a result that is initially unknown, usually because the computation of its value is not yet complete.

有道翻译:他们描述了一个对象,该对象充当最初未知的结果的代理,通常是因为其值的计算尚未完成。

“代理”这个词用的挺好的。

(2)历史

promise 一词由丹尼尔·福瑞得曼和 David Wise 在1976年提出。

后来演化出别称:futuredelaydeferred,通常可以互换使用。

promise 起源于函数式编程和相关范例(如逻辑编程 ),目的是将值(future)与其计算方式(promise)分离,从而允许更灵活地进行计算。

应用场景:

  • 并行化计算

  • 分布式计算

  • 编写异步程序,避免回调地狱

(3)各语言支持

现在主流的语言对 future/promise 都有支持。

  • Java 5 中的 FutureTask(2004年公布)

  • .NET 4.5 中的 async / await

  • Dart(2014)

  • Python(2015)

  • Hack(HHVM)

  • ECMAScript 7(JavaScript)

  • Scala

  • C++ 草案

  • ……

2、Promises/A+

官方:https://promisesaplus.com/

介绍:An open standard for sound, interoperable JavaScript promises—by implementers, for implementers.

可以理解成 javascript 中 关于 promise 的实现标准。

3、 拓展 - jQuery 中的 Promise

(1)介绍

从 jQuery 1.5.0 版本开始引入的一个新功能 —— deferred 对象。

注意:Deferred 虽然也是一种 promise 的实现,但是跟 Promise/A+ 并不兼容

但可以将其转为标准的 promise,例如:

var jsPromise = Promise.resolve($.ajax('/whatever.json'))
(2)用法

因为 jQuery 现如今很少用到了,仅简单介绍下 deferred 的用法吧。

1、以 ajax 操作为例:

$.ajax() 操作完成后,如果使用的是低于1.5.0版本的 jQuery,返回的是 XHR 对象,你没法进行链式操作;如果高于 1.5.0 版本,返回的是 deferred 对象,可以进行链式操作。

# old
$.ajax({     url: "test.html",     success: function(){
      alert("哈哈,成功了!");
    },     error:function(){
      alert("出错啦!");
    }   }); # new
$.ajax("test.html")   .done(function(){ alert("哈哈,成功了!"); })   .fail(function(){ alert("出错啦!"); });

2、其它

  • $.when() 类似 promise.all()

  • deferred.resolve()deferred.reject() 类似 Promise.resolve()、Promise.reject()

  • ……

三、bluebird


1、介绍

英文文档:

http://bluebirdjs.com/docs/api-reference.html

中文文档:

https://itbilu.com/nodejs/npm/VJHw6ScNb.html

2、安装

npm install bluebird

3、使用

const Promise = require('bluebird')

这样写会覆盖原生的 Promise 对象。

4、早期原生性能问题

早期 js 标准库里并没有包含 Promise,所以被迫只能用第三方的 Promise 库,例如 bluebird。

后来 ES6 和 ES7 相继推出了原生的 Promise 和 async/await ,但性能很差,大家还习惯用例如bluebird。

但到了 Node.js v8.x ,原生性能已经得到了很大的优化,可以不需要使用 bluebird 这样的第三方 Promise 库。(除非需要用到 bluebird 的更多 feature,而原生是不具备的。这个下面会详细介绍)

详情可以参考这篇文章:Node 8:迎接 async await 新时代

四、bluebird 用法


这一章,会结合 bluebird 用法 和 原生(主要以 ES7 的 async / wait) 探讨出最优写法。

1、回调形式 -> Promise 形式

大部分 NodeJS 的标准库 API 和不少第三方库的 API 都使用了回调方法的模式,也就是在执行异步操作时,需要传入一个回调方法来接受操作的执行结果和可能出现的错误。

例如 NodeJS 的标准库中的 fs 模块:

const fs = require('fs'),
path = require('path'); fs.readFile(path.join(__dirname, 'sample.txt'), 'utf-8', (err, data) => {
if (err) {
console.error(err);
} else {
console.log(data);
}
});
(1)bluebird

对于这样的方法,bluebird 的 promisifyAll()promisify() 可以很容易的将它们转换成使用 Promise 的形式。

// 覆盖了原生的Promise
const Promise = require('bluebird'),
fs = require('fs'),
path = require('path'); // 1、promisifyAll
// Promise.promisifyAll 方法可以为一个对象的属性中的所有方法创建一个对应的使用 Promise 的版本
Promise.promisifyAll(fs);
// 这些新创建方法的名称在已有方法的名称后加上"Async"后缀
// (除了 readFile 对应的 readFileAsync,fs 中的其他方法也都有了对应的 Async 版本,如 writeFileAsync 和 fstatAsync 等)
fs.readFileAsync(path.join(__dirname, 'sample.txt'), 'utf-8')
.then(data => console.log(data))
.catch(err => console.error(err)); // 2、promisify
// Promise.promisify 方法可以为单独的方法创建一个对应的使用 Promise 的版本
let readFileAsync = Promise.promisify(fs.readFile)
readFileAsync(path.join(__dirname, 'sample.txt'), 'utf-8')
.then(data => console.log(data))
.catch(err => console.error(err));
(2)原生

在 node.js 8.x版本中,可以用 util.promisify() 实现 promisify() 一样的功能。

在官方推出这个工具之前,民间已经有很多类似的工具了,除了bluebird.promisify,还有比如es6-promisify、thenify。

2、使用 promise —— .finally()

.finally() 可以避免同样的语句需要在 then() 和 catch() 中各写一次的情况。

(1)bluebird
Promise.reject(new TypeError('some error'))
.catch(TypeError, console.error)
.finally(() => console.log('done'));
(2)自己实现
Promise.prototype.finally = function (callback) {
return this.then(function (value) {
return Promise.resolve(callback()).then(function () {
return value;
});
}, function (err) {
return Promise.resolve(callback()).then(function () {
throw err;
});
});
};
(3)async / await

用 try...catch...finally 的 finally 即可实现。

(4)原生

.finally() 是ES2018(ES9)的新特性。

3、使用 promise —— .cancel()

(1)bluebird

当一个 Promise 对象被 .cancel() 之后,只是其回调方法都不会被调用,并不会取消正在进行的异步操作

// 先修改全局配置,让 promise 可被撤销
Promise.config({
cancellation: true, // 默认为 false
}); // 构造一个 promise 对象,并设置 1000 ms 延迟
let promise = Promise.resolve("hello").then((value) => {
console.log("promise 的 async function 还是执行了……")
return value
}).delay(1000) // promise 对象上绑定回调函数
promise.then(value => console.log(value)) // 取消这个 promise 对象的回调
setTimeout(() => {
promise.cancel();
}, 500); 输出:
promise 的 async function 还是执行了……

这里提到的 .delay() 方法下面会介绍。

(2)async / await

可以通过对 async / await 函数调用后的返回值,做 if 判断,决定要不要执行接下来的逻辑。

4、处理 promise 集合

之前的代码示例都针对单个 Promise。在实际中,经常会处理与多个 Promise 的关系。

(1)bluebird

以 fs 模块分别读取 sample1.txtsample2.txtsample3.txt 三个文件的内容为例。他们的文件内容分别为 “1”、“2”、“3”。


const Promise = require('bluebird'),
fs = require('fs'),
path = require('path');
Promise.promisifyAll(fs); // 一、并行操作 // 1、Promise.all ,必须全部成功才通过 【保证返回顺序】
Promise.all([
fs.readFileAsync(path.join(__dirname, 'sample1.txt'), 'utf-8'),
fs.readFileAsync(path.join(__dirname, 'sample2.txt'), 'utf-8'),
fs.readFileAsync(path.join(__dirname, 'sample3.txt'), 'utf-8')
]).then(results => console.log(results.join(', '))).catch(console.error); // 1.1、Promise.props ,约等于 Promise.all,但不同的在于: 返回的不是数组而是对象 !
Promise.props({
app1: fs.readFileAsync(path.join(__dirname, 'sample1.txt'), 'utf-8'),
app2: fs.readFileAsync(path.join(__dirname, 'sample2.txt'), 'utf-8'),
app3: fs.readFileAsync(path.join(__dirname, 'sample3.txt'), 'utf-8'),
}).then(results => console.log(results)).catch(console.error); // 1.2 Promise.join,约等于 Promise.all 【保证返回顺序】, 但不同的在于: 成功结果不是 array 而是多个参数 !
Promise.join(
fs.readFileAsync(path.join(__dirname, 'sample1.txt'), 'utf-8'),
fs.readFileAsync(path.join(__dirname, 'sample2.txt'), 'utf-8'),
fs.readFileAsync(path.join(__dirname, 'sample3.txt'), 'utf-8'),
(a, b, c) => console.log(a, b, c)); // 1.3、Promise.filter ,约等于 Promise.all 之后对成功结果的 Array 进行 filter 过滤 【保证返回顺序】
Promise.filter([
fs.readFileAsync(path.join(__dirname, 'sample1.txt'), 'utf-8'),
fs.readFileAsync(path.join(__dirname, 'sample2.txt'), 'utf-8'),
fs.readFileAsync(path.join(__dirname, 'sample3.txt'), 'utf-8')
], value => value > 1).then(results => console.log(results.join(', '))).catch(console.error); // ---------- // 2、Promise.map ,约等于 Promise.all 【保证返回顺序】
Promise.map(['sample1.txt', 'sample2.txt', 'sample3.txt'],
name => fs.readFileAsync(path.join(__dirname, name), 'utf-8')
).then(results => console.log(results.join(', '))).catch(console.error); // 2.1 Promise.reduce,约等于 Promise.map
Promise.reduce(['sample1.txt', 'sample2.txt', 'sample3.txt'],
(total, name) => {
return fs.readFileAsync(path.join(__dirname, name), 'utf-8').then(data => total + parseInt(data));
}
, 0).then(result => console.log(`Total size: ${result}`)).catch(console.error); // ---------- // 3、Promise.some 只要成功 N 个就通过 【不保证返回顺序】
Promise.some([
fs.readFileAsync(path.join(__dirname, 'sample1.txt'), 'utf-8'),
fs.readFileAsync(path.join(__dirname, 'sample2.txt'), 'utf-8'),
fs.readFileAsync(path.join(__dirname, 'sample3.txt'), 'utf-8')
], 3).then(results => console.log(results.join(', '))).catch(console.error); // 3.1、Promise.any 只要成功 1 个就通过,约等于 Promise.some (N = 1),但不同的在于:返回的不是数组而是单个值了!
Promise.any([
fs.readFileAsync(path.join(__dirname, 'sample1.txt'), 'utf-8'),
fs.readFileAsync(path.join(__dirname, 'sample2.txt'), 'utf-8'),
fs.readFileAsync(path.join(__dirname, 'sample3.txt'), 'utf-8')
]).then(results => console.log(results)).catch(console.error); // 3.2、Promise.race 只要成功 1 个就通过,约等于 Promise.any (N = 1),但不同的在于:如果成功返回前遇到了失败,则会不通过!
Promise.race([
fs.readFileAsync(path.join(__dirname, 'sample1.txt'), 'utf-8'),
fs.readFileAsync(path.join(__dirname, 'sample2.txt'), 'utf-8'),
fs.readFileAsync(path.join(__dirname, 'sample3.txt'), 'utf-8')
]).then(results => console.log(results)).catch(console.error); // ---------- // 二、串行 // 4、Promise.mapSeries ,约等于 Promise.map 【保证返回顺序】,但不同的在于: 这是串行不是并行!
Promise.mapSeries(['sample1.txt', 'sample2.txt', 'sample3.txt'],
name => fs.readFileAsync(path.join(__dirname, name), 'utf-8').then(function(fileContents) {
return name + "!";
})
).then(results => console.log(results.join(', '))).catch(console.error);
// 'sample1.txt!, sample2.txt!, sample3.txt!' // 4.1、Promise.each ,约等于 Promise.mapSeries 【保证返回顺序】, 但不同的在于: 只是单纯的遍历,每次循环的 return 毫无影响 !
Promise.each(['sample1.txt', 'sample2.txt', 'sample3.txt'],
name => fs.readFileAsync(path.join(__dirname, name), 'utf-8').then(function(fileContents) {
return name + "!"; // 无效
})
).then(results => console.log(results.join(', '))).catch(console.error);
// 'sample1.txt, sample2.txt, sample3.txt'

1、大多数函数都是并行的。其中 map、filter 还有 Concurrency coordination (并发协调)功能。

注意:

1、因为 Node.js 是单线程,这里的并发只是针对 promise 而言,实际上底层还是串行

2、并发数的多少,取决于你 promise 执行的具体功能,如网络请求、数据库连接等。需根据实际情况来设置。

以 map 为例:

// 控制并发数
Promise.map(['sample1.txt', 'sample2.txt', 'sample3.txt'],
name => fs.readFileAsync(path.join(__dirname, name), 'utf-8'),
{concurrency: 2}
).then(results => console.log(results.join(', '))).catch(console.error);

2、mapSeries、each 是串行,也可以看成是 {concurrency: 1} 的特例。

(2)拓展 - promiseAll 实现原理
function promiseAll(promises) {
return new Promise(function(resolve, reject) {
if (!isArray(promises)) {
return reject(new TypeError('arguments must be an array'));
}
var resolvedCounter = 0;
var promiseNum = promises.length;
var resolvedValues = new Array(promiseNum);
for (var i = 0; i < promiseNum; i++) {
(function(i) {
Promise.resolve(promises[i]).then(function(value) {
resolvedCounter++
resolvedValues[i] = value
if (resolvedCounter == promiseNum) {
return resolve(resolvedValues)
}
}, function(reason) {
return reject(reason)
})
})(i)
}
})
}

注意:Promise.resolve(promises[i])这段的意思,是防止 promises[i] 为非 promise 对象,而强制转成 promise 对象。

此源码地址为: promise-all-simple

(3)async / await

对于上面的并行操作,建议用 bluebird (原生貌似现在只支持 Promise.all() ,太少了)。

对于上面的串行操作,可以用 循环 搭配 async / await 即可。

5、资源使用与释放

如果在 Promise 中使用了需要释放的资源,如数据库连接,我们需要确保这些资源被应有的释放。

(1)bluebird

方法1:finally() 中添加资源释放的代码(上文有介绍)

方法2【推荐】:使用资源释放器(disposer)和 Promise.using()。

(2)async / await

利用 async / await 中的 try...catch...finally 中的 finally

6、定时器

(1)bluebird
async function test() {
try {
let readFilePromise = new Promise((resolve, reject) => {resolve('result')})
let result = await readFilePromise.delay(1000).timeout(2000, 'timed out')
console.log(result);
} catch (err) {
console.log("error", err);
}
} test();

1、默认的, new Promise 会立即执行,但是加了 delay(),可以延迟执行。

2、timeout() 可以设置执行的 timeout 时间,超过即抛出 TimeoutError 错误。

(2)async / await

暂时没有方便的替代写法。

7、实用方法

(1)bluebird

bluebird 的 Promise 中还包含了一些实用方法。taptapCatch 分别用来查看 Promise 中的结果和出现的错误。这两个方法中的处理方法不会影响 Promise 的结果,适合用来执行日志记录。call 用来调用 Promise 结果对象中的方法。get 用来获取 Promise 结果对象中的属性值。return 用来改变 Promise 的结果。throw 用来抛出错误。catchReturn 用来在捕获错误之后,改变 Promise 的值。catchThrow 用来在捕获错误之后,抛出新的错误。

(2)async / await

上面 bluebird 的实用方法,在 async / await 的写法里,显得无足轻重了。

8、错误处理

(1)拓展 - then() 的多次指定与报错

对一个 resolve 的 promise ,指定多个 then:

let promiseObj = new Promise((resolve, reject) => {resolve()})

// 第一次指定 then
promiseObj.then(function (data) {
console.log("success1");
}, function (data) {
console.log("fail1");
})
// 第二次指定 then
promiseObj.then(function (data) {
console.log("success2");
}, function (data) {
console.log("fail2");
}) // 第三次指定 then
promiseObj.then(function (data) {
console.log("success3");
}) // 第四次指定 then(catch)
promiseObj.catch(function (data) {
console.log("fail4");
}) 输出:
success1
success2
success3

对一个 reject 的 promise ,指定多个 then:

let promiseObj = new Promise((resolve, reject) => {reject()})

// 第一次指定 then
promiseObj.then(function (data) {
console.log("success1");
}, function (data) {
console.log("fail1");
})
// 第二次指定 then
promiseObj.then(function (data) {
console.log("success2");
}, function (data) {
console.log("fail2");
}) // 第三次指定 then
promiseObj.then(function (data) {
console.log("success3");
}) // 第四次指定 then(catch)
promiseObj.catch(function (data) {
console.log("fail4");
}) 输出:
fail1
fail2
fail4
Unhandled rejection undefined

结论:

1、对于一个 promise 对象,我们可以多次指定它的 then()。

2、当此 promise 状态变为 resolve,即使没有 then() 或者 有 then() 但是没有 successCallback,也不会有问题。

3、当此 promise 状态变为 reject, 如果没有 then() 或者有 then() 但是没有 failureCallback ,则会报错(下面会介绍如何捕获这个错)。

(2)bluebird

1、本地错误处理

利用 then() 的 failureCallback(或 .catch() )。不赘述了。

2、全局错误处理

bluebird 提供了 promise 被拒绝相关的两个全局事件,分别是 unhandledRejectionrejectionHandled

let promiseObj = new Promise((resolve, reject) => {reject('colin')})

setTimeout(() => {
promiseObj.catch(function (data) {
console.log("fail");
})
}, 2000); process.on('unhandledRejection', (reason, promise) => console.error(`unhandledRejection ${reason}`)); process.on('rejectionHandled', (reason, promise) => console.error(`rejectionHandled ${reason}`)); 输出:
unhandledRejection colin
rejectionHandled [object Promise]
fail

1、promise 的 reject 没有被处理(即上面所述),则会触发 unhandledRejection 事件

2、但可能 针对 reject 的处理延迟到了下一个事件循环才被执行,那就会触发 rejectionHandled 事件

所以我们得多等等 rejectionHandled 事件,防止误判,所以可以写成下面全局错误处理的代码:

let possiblyUnhandledRejections = new Map();
// 当一个拒绝未被处理,将其添加到 map
process.on("unhandledRejection", function(reason, promise) {
possiblyUnhandledRejections.set(promise, reason);
});
process.on("rejectionHandled", function(promise) {
possiblyUnhandledRejections.delete(promise);
});
setInterval(function() {
possiblyUnhandledRejections.forEach(function(reason, promise) {
// 做点事来处理这些拒绝
handleRejection(promise, reason);
});
possiblyUnhandledRejections.clear();
}, 60000);
(3)async / await 的错误处理

async / await 的 try..catch 并不能完全捕获到所有的错误。

1、本地错误处理

用 try...catch 即可。

注意:漏掉错误 情况:

run() 这个 promise 本身 reject 了

async function run() {
try {
// 注意这里没有 await
return Promise.reject();
} catch (error) {
console.log("error",error)
// 代码不会执行到这里
}
}
run().catch((error) => {
    // 可以捕获
console.log("error2", error)
});

解决方法:针对 run() 函数 (顶层函数)做好 catch 捕获。

2、全局错误处理

漏掉错误 情况:

run() 这个 promise 内部存在 reject 但没有被处理的 promise

async function run() {
try {
// 注意这里 即没有 await 也没有 return
Promise.reject();
} catch (error) {
console.log("error", error)
// 代码不会执行到这里
}
}
run().catch((error) => {
    // 不可以捕获
console.log("error2", error)
});

解决方法:

1、跟上面介绍的 bluebird 全局错误处理一样,用好unhandledRejectionrejectionHandled 全局事件。

2、ES6 原生也支持 unhandledRejectionrejectionHandled 全局事件。


参考资料

使用 bluebird 实现更强大的 Promise

promise 进阶 —— async / await 结合 bluebird的更多相关文章

  1. 异步操作之 Promise 和 Async await 用法进阶

    ES6 提供的 Promise 方法和 ES7 提供的 Async/Await 语法糖都可以更好解决多层回调问题, 详细用法可参考:https://www.cnblogs.com/cckui/p/99 ...

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

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

  3. node.js异步控制流程 回调,事件,promise和async/await

    写这个问题是因为最近看到一些初学者用回调用的不亦乐乎,最后代码左调来又调去很不直观. 首先上结论:推荐使用async/await或者co/yield,其次是promise,再次是事件,回调不要使用. ...

  4. Promise及Async/Await

      一.为什么有Async/Await? 我们都知道已经有了Promise的解决方案了,为什么还要ES7提出新的Async/Await标准呢? 答案其实也显而易见:Promise虽然跳出了异步嵌套的怪 ...

  5. callback vs async.js vs promise vs async / await

    需求: A.依次读取 A|B|C 三个文件,如果有失败,则立即终止. B.同时读取 A|B|C 三个文件,如果有失败,则立即终止. 一.callback 需求A: let read = functio ...

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

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

  7. 异步Promise及Async/Await最完整入门攻略

    一.为什么有Async/Await? 我们都知道已经有了Promise的解决方案了,为什么还要ES7提出新的Async/Await标准呢? 答案其实也显而易见:Promise虽然跳出了异步嵌套的怪圈, ...

  8. “setTimeout、Promise、Async/Await 的区别”题目解析和扩展

    解答这个题目之前,先回顾下JavaScript的事件循环(Event Loop). JavaScript的事件循环 事件循环(Event Loop):同步和异步任务分别进入不同的执行"场所& ...

  9. 异步Promise及Async/Await可能最完整入门攻略

    此文只介绍Async/Await与Promise基础知识与实际用到注意的问题,将通过很多代码实例进行说明,两个实例代码是setDelay和setDelaySecond. tips:本文系原创转自我的博 ...

随机推荐

  1. Linux(ubuntu)下切换root用户

    输入命令su root切换用户,会提示输入root密码,如果不记得或者是没设置过,那么可以输入sudo passwd root来设置密码,会让你输入两次密码确认.输入完即可使用su root命令切换r ...

  2. 分组取topN

    假设有这样一个文件,文件内容如下 class1 class2 class1 class1 class2 class2 class1 class2 class1 class2 要求按照班级分组取出每个班 ...

  3. GCD 面试题

    今天我们讲解几道这两天遇到的面试题--GCD编程的.题目很不错,很考究关于GCD的基本概念和使用. 对于基本的概念,本人博客已在前面讲过,本篇主要以面试题来讲解.大家可看一下本人关于GCD的基本讲解  ...

  4. hostnamectl命令 主机名 host相关命令

    hostnamectl set-hostname CentOS7设置主机名为CentOS7 hostnamectl status查看主机系统信息 注:host+TAB查阅host相关的所有命令 hos ...

  5. php使用cUrl方法 get、post请求

    php使用curl方法,请确保已经开启curl扩展.传送门:http://www.cnblogs.com/wgq123/p/7450667.html /**Curl请求get方法 *@$url Str ...

  6. selenium中延时等待三种方式

    selenium中的延时等待方式有三种:强制等待:sleep()  隐示等待:implicitly_wait()  显示等待 WebDriverWait() 1.强制等待:sleep(),time模块 ...

  7. D^3ctf两道 pwn

    这次 的D^3ctf 又是给吊打 难顶... 所以题都是赛后解出来的,在这感谢Peanuts师傅 unprintableV 看看保护: 看看伪代码,其实代码很少 void __cdecl menu() ...

  8. php+redis实现注册、删除、编辑、分页、登录、关注等功能

    本文实例讲述了php+redis实现注册.删除.编辑.分页.登录.关注等功能.分享给大家供大家参考,具体如下: 主要界面 ​ 连接redis redis.php <?php //实例化 $red ...

  9. LinkedList实现原理(JDK1.8)

    LinkedList实现原理(JDK1.8) LinkedList底层采用双向链表,如果对链表这种结构比较熟悉的话,那LinkedList的实现原理看明白就相当容易. 链表通过"指针&quo ...

  10. 实战webpack系列说明

    01.概念股 本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler). 当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(d ...