1.函数式编程

1.1高阶函数

函数参数只接受基本数据类型或者对象引用,返回值也是基本数据类型和对象引用。

//常规参数传递和返回
function foo(x) {return x;
}
复制代码

高阶函数则是可以把函数作为参数和返回值的函数。

function foo(x) {return function() {return x}
}
复制代码
function foo(x, bar) {return bar(x);
}
复制代码

上面这个函数相同的foo函数但是传入的bar参数不同则可以返回不同的结果,列如数组的sort()方法,它是一个高阶函数,接受一个参数方法作为排序运算。

var arr = [40, 80, 60, 5, 30, 77];
arr.sort(function(a, b) {return a - b;
});
//运行结果[5, 30, 40, 60, 77, 80];
复制代码

通过修改sort方法的参数可以决定不同的排序方法,这就是高阶函数的灵活性。
结合Node的基本事件模块可以看到,事件处理方式正是基于高阶函数的特性来完成的。在自定义事件中,通过为相同的事件注册不同的回调函数,可以很灵活的处理业务逻辑。

var emit = new events.EventEmitter();
emit.on('event', function() {//do something
});
复制代码

1.2偏函数

偏函数指的是创建一个调用另外一部分(参数或者变量已经预置的函数)的函数的用法例如:

var toString = Object.prototype.toString;
var isString = function(obj) {return toString.call(obj) == '[object String]';
}
var isFunction = function(obj) {return toString.call(obj) == '[object Function]';
}
复制代码

这段代码用于判断类型,通常会进行上述定义,代码存在相似性如果要判断更多会定义更多的isXXX()方法,这样就会出现冗余代码。为了解决重复问题,引入一个新函数用于批量创建这样的类似函数。

var isType = function (type) {return function(obj) {return toString.call(obj) == '[object '+ type +']';}
}
var isString = isType('String');
var isFunction = isType('Function');
复制代码

这种通过指定部分参数来产生一个新的定制函数的形式就是偏函数。

2.异步编程优势与难点

2.1异步优势

Node的最大特性是基于事件驱动的非阻塞I/O模型,这使得CPU和I/O不互相依赖,让资源更好的利用,对于网络应用而言使得各个单点之间可以更有效的组织起来,这使得Node在云计算中广受青睐。 由于事件循环模型要应对海量请求,所有请求作用在单线程上需要防止任何一个计算过多的消耗CPU时间片。建议计算对CPU的耗用不超过10ms,或将大量的计算分解成小量计算,通过setImmediate()进行调度。

2.2难点

1.异常处理

通常处理异常时使用 try/catch/final语句进行异常捕获:

try {JSON.parse(str);
}catch(e) {console.log(e)
}
复制代码

但这对于异步编程不一定适用。I/O实现异步有两个阶段:提交请求和处理结果。这两个阶段中间有事件循环调度,彼此互不关联,一步方法通常在第一个阶段请求提交后立即返回,但是错误异常并不一定发生在这个阶段,try/catch就不一定会发生作用了。

var async = function(callback) {process.nextTick(callback);
}try {async(callback);
}catch(e) {}
复制代码

调用async方法后callback会被存起来知道下一个事件循环才被执行,try/catch操作只能捕获当前时间循环内的异常。对callback中的异常不起作用。
Node在处理异常上形成了一个约定,将异常作为回调的第一个参数传回,如果是空值,表明没有异常抛出:

async(function(err, res)) {});
复制代码

在自行编写的异步方法上也需要去遵循这样的原则: 1.必须执行调用者传入的回调函数; 2.正确的传回异常供调用者判断;

var async = function(callback) {process.nextTick(function() {var res = 'something';if(error) {return callback(error);}else {return callback(null, res);}})
}
复制代码

在异步编程中,另一个容易犯的错误是对用户传递的callback进行异常捕获,

try {req.body = JSON.parse(buf, options.reviver);callback();
}catch(e) {err.body = buf;err.status = 400;callback(e);
}
复制代码

如果JSON.parse出现错误代码将进入catch部分这样回调函数callback将被执行两次,正确的做法应该是

try {req.body = JSON.parse(buf, options.reviver);
}catch(e) {err.body = buf;err.status = 400;return callback(e);
}
callback();
复制代码
2.函数嵌套过深

Node中事物中会出现多个异步调用的场景,列如遍历目录:

fs.readdir('path', function(err, files) {files.forEach(function(fileName, index) {fs.readFile(fileName, 'utf8', function(err, file) {//TODO})})
});
复制代码

上述操作由于两次操作存在依赖关系,函数嵌套行为情有可原,但是在某些场景列如渲染网页:通常需要数据、模版、资源文件,这三个操作互不依赖但是最终结果又是三者不可缺一,如果采用默认异步调用会是这样:

fs.readFile('path', 'utf8', function(err, templete) {db.query(sql, function(err, data) {l10n.get(function(err, res) {//TODO})   })
})
复制代码

这样导致了代码嵌套过深,不易读且不好维护。

3.阻塞代码

javascript没有sleep()这样让线程沉睡的功能,只有setInterval()和setTimeout()这两个函数,但是这两个函数不能阻塞后面的代码执行。

4.多线程编程

说到javascript时候,通常谈的是单线程上执行。随着业务复杂,对于多核CPU的利用要求也越来越高。浏览器中提出了Web Workers。可以更好的利用多核CPU为大量计算服务。前端Web Workers也是利用消息机制合理的使用多核CPU的理想模型。

Web Workers的工作示意图
Node借鉴了这个模式,child_process是其基础API,cluster模块是更深层次的应用。

5.异步转同步

Node提供了绝大部分的异步API和少量的同步API,Node中试图同步编程,但并不能得到原生支持,需要借助库或者编译手段实现。

3.异步编程解决方案

目前,异步编程主要解决方案有三种:

  1. 事件发布/订阅模式
  2. Promise/Deferred模式
  3. 流程控制库

3.1事件发布/订阅模式

Node自身提供的events模块是发布/订阅的一个简单实现,Node中部分模块都继承自它。它具有addListener/on()、 once()、 removeListener()、 removeAllListener()、 emit() 等基础方法。

//订阅
emitter.on('event', function(msg) {console.log(msg)
});
emitter.emit('event', 'this is msg');
复制代码

Node对事件发布/订阅的机制做了一些额外处理:

  1. 如果对一个事件添加超过10个侦听器,将会得到一条警告,设计者认为太多的侦听器可能会导致内存泄漏,调用emitter.setMaxListener(0);可以去掉这个限制。
  2. 为了异常处理,EventEmitter对象对error事件进行了特殊对待。如果运行期间的错误出发了error事件,EventEmitter会检测是否对error事件添加过侦听器,如果加了,这个错误将交给侦听器处理,否则将作为异常抛出。如果外部没有捕获异常,将会引起线程退出。
1.继承events模块

实现一个继承EventEmitter的类:

var events = require('events');
function Stream() {events.EventEmitter.call(this);
}
util.inherits(stream, events.EventEmitter);
复制代码

Node在util模块封装了继承方法。

2.利用事件列队结局雪崩问题

在事件订阅/发布模式中,通常有一个once()方法,通过它添加的侦听器只能执行一次,执行后将被移除。
在计算机中,缓存由于放在内存中,访问书的快,用于加速数据访问,让绝大多数请求不必重复去做一些低效的数据读取。所谓的雪崩问题,就是高访问量、大并发量的情况下缓存失效的情况,此时大量的请求同时涌入数据库中,数据库无法承受大量查询请求进而影响网站整体响应速度。

//查询数据库
var select = function(callback) {db.select(sql, function(err, res) {callback(res);})
}
复制代码

如果站点刚启动缓存中还没有数据,如果访问量巨大,同一句sql会被执行多次反复查询数据库,将会影响性能。改进方案:加一个状态锁

var status = 'ready';
var select = function(callback) {if(status === 'ready') {status = 'pending';db.select(sql, function(err, res) {status = 'ready';callback(res);})}
}
复制代码

但是在这种情况下连续多次调用只有第一次调用是生效的,后续调用是没有数据服务的,这个时候可以引入事件列队:

var proxy = new events.EventEmitter();
var status = 'ready';
var select = function(callback) {proxy.once('selected', callback);if(status == 'ready') {status = 'pending';db.select(sql, function(err, res) {proxy.emit('selected')status = 'ready';})}
}
复制代码

利用once()方法,将所有请求的回调压入事件列队,利用其执行一次就好将监视器移除的特点,保证一次回调只会被执行一次。

3.多异步之间的协作方案

以上面提到的渲染网页(模版读取、数据读取、本地资源读取)为例:

var count = 0;
var res = {};
var done = function(key, val) {res[key] = val;count ++;if(count === 3) {render(res);}
};
fs.readFile(template_path, 'utf8', function(err, tp) {done('tp', tp);
});
db.query(sql, (err, data) {done('data', data);
});
l10n.get(function(err, res) {done('res', res);
});
复制代码

通常用于检测次数的变量叫做'哨兵变量'。利用偏函数来处理哨兵变量和第三方函数的关系:

var after = function(times, callback) {var count = 0,res = {};return function(key, val) {res[key] = val;count ++;if(count == times) {callback(res);}}
}var emitter = new events.EventEmitter();
var done = after(times, render);emitter.on('done', done);
emitter.on('done', other);fs.readFile(template_path, 'utf8', function(err, tp) {emitter.emit('done', 'tp', tp);
});
db.query(sql, (err, data) {emitter.emit('done', 'data', data);
});
l10n.get(function(err, res) {emitter.emit('done', 'res', res);
});
复制代码
4.EventProxy

扑灵写的EventProxy模块,是对事件订阅/发布模式的扩充

var proxy = new EventProxy();
proxy.all('tp', 'data', 'res', function(tp, data, res) {//TODO
});
fs.readFile(template_path, 'utf8', function(err, tp) {proxy.emit('tp', tp);
});
db.query(sql, (err, data) {proxy.emit('data', data);
});
l10n.get(function(err, res) {proxy.emit('res', res);
});
复制代码

EventProxy提供了all()方法来订阅多个事件,所有事件触发后侦听器才会被触发。另一个tail()方法在满足条件时只需一次后,如果组合事件中的某个事件再次被触发,侦听器会用最新的数据继续只需。
after()方法:实现事件在执行多少次后执行侦听器的单一事件组合订阅方式:

//执行10次data事件后触发侦听器
var proxy = new EventProxy();
proxy.after('data', 10, function(datas) {//TODO
})
复制代码

EventProxy原理
EventProxy来源自Backbone的事件模块,它在每个非all的事件触发时都会触发一次all事件

trigger: functuon(eventName) {var list, calls, ev, callback, args;var both = 2;if(!(calls = this._callbacks)) return;while (both--) {ev = both?eventName:'all';if(list = calls[ev]) {for(var i = 0, l = list.length; i < 1; i ++) {if(!(callback = list[i])) {list.splice(i, i);i --;l --;}else {args = both? Array.prototype.slice.call(arguments, 1):argument;callback[0].apply(callback[1] || this, args);}}}}return this;
}
复制代码

EventProxy则是将all当做一个事件流的拦截层,在其中注入一些业务来处理单一事件无法解决的异步处理问题。

5.EventProxy异常处理

EventProxy提供了fail()和done()两个实例方法来优化异常处理。

var proxy = new EventProxy();
proxy.all('tp', 'data', function(tp, data, res) {//TODO
});
proxy.fail(function(err) {//错误处理
})
fs.readFile(template_path, 'utf8', proxy.done('tp'));
db.query(sql, proxy.done('data'));proxy.done('tp')等价于
function(err, data) {if(err) {return proxy.emit('error', err)}proxy.emit('tp', data)
}
复制代码

3.2.Promise/Deferred模式

使用事件的方式时,执行的流程被预先设定。Promise/Deferred模式先执行异步调用,延迟传递处理方式。

//普通jquery Ajax调用
$.get('api',{success: onSuccess,error: onError,complete: onComplete
})
复制代码

需要提前预设对应的回调函数

//Promise/Deferred模式 jquery Ajax调用
$.get('api').success(onSuccess).error(onError).complete(onComplete);
复制代码

Promise/Deferred模式即使不传入回调函数也能执行,传统方法一个事件只能传入一个回调函数,而Deferred对象可以对事件加入任意业务处理逻辑。

$.get('api').success(onSuccess1).success(onSuccess2);
复制代码

CommonJS抽象出了 Promises/A,Promises/B,Promises/D这样的典型异步Promise/Deferred模型。

1.Promises/A

Promises/A提议对单个异步操作做出这样的定义:

  1. Promise操作只会处在3种状态的一种:未完成状态、完成状态、失败状态。
  2. Promise的状态只会出现从未完成向完成或者失败状态转换,不能逆向。完成和失败不能互相转换。
  3. Promise状态一旦转换将不能被更改。

Promise状态转换示意图

Promises/A API定义比较简单。一个Promise对象只要具备then()方法即可。对应then()的要求:

  1. 接受完成、错误的回调方法。操作完成或错误时,将会调用对应方法。
  2. 可选的支持progress事件回调作为第三个方法。
  3. then()方法只接受function对象,其他的会被忽略。
  4. then()方法继续返回Promise对象,实现链式调用。

then()方法定义:

then(fulfilledHandler, errorHandler, progressHandler)
复制代码

Promises/A演示:

var Promise = function() {EventEmitter.call(this);
};
util.inherit(Promise, EventEmitter);Promise.prototype.then = function(fulfilledHandler, errorHandler, progressHandler) {if(typeof fulfilledHandler === 'function') {this.once('success', fulfilledHandler)}if(typeof errorHandler === 'function') {this.once('error', errorHandler)}if(typeof progressHandler === 'function') {this.on('progress', progressHandler)}return this;
}
复制代码

then()方法将回调函数存放起来,为了完成整改流程,还需要触发执行这些函数的地方,实现这些功能的对象被称为Deferred,即延迟对象:

var Deferred = function() {this.state = 'unfulfilled';this.promise = new Promise();
}
Deferred.prototype.resolve = function(obj) {this.state = 'fulfilled';this.promise.emit('success', obj);
}
Deferred.prototype.reject = function(obj) {this.state = 'faild';this.promise.emit('error', obj);
}
Deferred.prototype.progress = function(obj) {this.promise.emit('progress', obj);
}
复制代码

状态与方法对应关系
利用Promises/A模式,对一个典型的响应对象进行封装:

res.setEncoding('utf8');
res.on('data', function(chunk) {//成功console.log(chunk)
});
res.on('end', function() {//失败
})
res.on('error', function(err) {//progress
})
//通过改造
var promisify = function(res) {var deferred = new Deferred();var res = '';res.on('data', function(chunk) {res += chunk;deferred.progress(chunk);})res.on('end', function() {promise.resovle(res);})res.on('error',function(err) {promise.reject(err)})return deferred.promise;
}
//简便写法
promisify(res).then(function() {//成功
},function(err) {//失败
}, function(chunk) {//progress
})
复制代码

从上面代码可以看出,Deferred主要用于内部,用于维护异步模型的状态;Promise则作用于外部,通过then()方法暴露给外部添加自定义逻辑。

Promise和Deferred整体关系示意图
于事件发布/订阅相比Promise/Deferred模式API接口更加简洁,它将不变的部分封装在Deferred中,将可变的部分交个Promise。

Q模块...

2.Promise中的多异步协作

简单原型实现:

Deferred.prototype.all = function(promises) {var count = promises.length;var that = this;var res = [];promises.forEach(function(promise, i) {promise.then(function(data) {count --;res[i] = data;if(count === 0) {that.resolve(res);}}, function(err) {that.reject(err);})return this.promise;})
}
复制代码

通过all()方法抽象多个异步操作。只有所有异步操作成功,这个异步操作才算成功,其中有一个失败,整个异步操作就失败。
(实际使用推荐使用when,Q模块,是对完整的Promise提议的实现)

3.Promise的进阶

现有一组纯异步的API,为完成一件串联事代码如下:

obj.api1(function(val1) {obj.api2(val1, function(val2) {obj.api3(val2, function(val3) {obj.api4(val3, function(val4) {callback(val4);})})})
})
复制代码

使用普通函数将上面代码展开:

var handler1 = function(val1) {obj.api2(val1, handler2);
}
var handler2 = function(val2) {obj.api3(val2, handler3);
}
var handler3 = function(val3) {obj.api4(val3, handler4);
}
var handler41 = function(val4) {callback(val4)
}
obj.api1(handler1);
复制代码

使用事件机制

var emitter = new EventEmitter();emitter.on('step1', function() {obj.api1(function(val1) {emitter.emit('step2', val1);})
})
emitter.on('step2', function(val1) {obj.api2(val1, function(val2) {emitter.emit('step3', val2);})
})
emitter.on('step3', function(val2) {obj.api3(val2, function(val3) {emitter.emit('step42', val3);})
})
emitter.on('step4', function(val3) {obj.api4(val3, function(val4) {callback(val4);})
})
emitter.emit('step1');
复制代码

使用事件后代码量明显增加,需要一种更好的方式。
支持序列执行的Promise
理想的方法---链式调用:

promise().then(obj.api1).then(obj.api2).then(obj.api3).then(obj.api4).then(function(val4) {},function(err) {}).done();
复制代码

通过改造代码以实现链式调用:

var Deferred = function() {this.promise = new Promise();
}
//完成状态
Deferred.prototype.resolve = function(obj) {var promise = this.promise;var handler;while((handler = promise.queue.shift())) {if(handler && handler.fulfilled) {var ret = handler.fulfilled(obj);if(ret && ret.isPromise) {ret.queue = promise.queue;this.promise = ret;return;}}}
}
//失败状态
Deferred.prototype.reject = function(err) {var promise = this.promise;var handler;while((handler = promise.queue.shift())) {if(handler && handler.error) {var ret = handler.error(err);if(ret && ret.isPromise) {ret.queue = promise.queue;this.promise = ret;return;}}}
}
//生成回调函数
Deferred.prototype.callback = function() {var that = this;return function(err, file) {if(err) {return that.reject(err);}else {that.resolve(file);}}
}var Promise = function() {this.queue = [];this.isPromise = true;
}Promise.prototype.then = function(fulfilledHandler, errorHandler, progressHandler) {var handler = {};if(typeof fulfilledHandler === 'function') {handler.fulfilled = fulfilledHandler;}if(typeof errorHandler === 'function') {handler.errorHandler = errorHandler;}this.queue.push(handler);return this;
}
复制代码

以两次文件读取为例,假设读取第二个文件是依赖第一个文件中的内容:

var readFile1 = function(file, encoding) {var deferred = new Deferred();fs.readFile(file, encoding, deferred.callback());return deferred.promise;
}
var readFile2 = function(file, encoding) {var deferred = new Deferred();fs.readFile(file, encoding, deferred.callback());return deferred.promise;
}
readFile1('file1.txt', 'utf8').then(function(file1) {return readFile2(file1.trim(), 'utf8');
}).then(function(file2) {//file2
})
复制代码

要让Promise支持链式执行,主要通过两个步骤。

  1. 将所有的回调都存到队列中。
  2. Promise完成时,逐个执行回调,一旦检测到返回了新的Promise对象,就停止执行,然后将当前Deferred对象的promise引用改为新的Promise对象,并将列队中余下的回调转给它。
    将API Promise化 为了更好体验的API,需要较多的准备工作。批量将方法Promise化:
//smooth(fs.readFile)
var smooth = function(method) {return function() {var deferred = new Deferred();var ags = Array.prototype.slice.call(argument, 1);args.push(deferred.callback());method.apply(null, args);return deferred.promise;}
}
复制代码

上面的两次读取文件可以简化为:

var readFile = smooth(fs.readFile);
readFile('file1.txt', 'utf8').then(function(file1) {return readFile(file1.trim(), 'utf8');
}).then(function(file2) {})
复制代码

3.3.ES6 Generator

书中没有提到这种模式,下面补充一下。
Generator函数是ES6提供的一个异步解决方案。最大的特点是可以暂停执行。 Generator函数和普通的函数区别有两个, 1:function和函数名之间有一个*号, 2:函数体内部使用了yield表达式
调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象(Iterator Object)需要调用遍历器的next()方法才能使函数继续执行,直到遇到yield方法再次暂停执行。

function* gen() {var a = 10;console.log(a);yield a ++;console.log(a);
}
复制代码

上面是一个Generator函数,运行:

var g = gen();
复制代码

并么有打印出a的值,执行gen()后只是得到了一个遍历器对象。

g.next();
//10
复制代码

执行遍历器的next()方法后输出了10。

g.next();
//11
复制代码

再次执行next(),yield后的语句被执行输出10。
遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值

使用ES6的Promise和Generator解决恶魔金字塔
function *readFileStep(path1) {let path2 = yield new Promise((resovle, reject) => {fs.readFile(path1, 'utf8', (err, data) => {resovle(data);});});let path3 = yield new Promise((resovle, reject) => {fs.readFile(path2, 'utf8', (err, data) => {resovle(data);});});return new Promise((resovle, reject) => {fs.readFile(path3, 'utf8', (err, data) => {resovle(data);});});
}
function run(it) {function go(result) {if (result.done) {return result.value;}return result.value.then(function(value) {return go(it.next(value));});}return go(it.next());
};run(readFileStep('./file1.txt')).then((data) => {console.log(data)
});
复制代码
  1. 定义一个Generator函数readFileStep,内含三个异步的读取文件yield函数,并且每个函数返回一个Promise。
  2. 实现一个执行器,获取每次异步执行返回的Promise对象的结果,如果结果返回后且遍历器不是完成状态就继续执行next()方法,直到完成返回最终的Promsie对象。
  3. 将Generator函数放入执行器中执行,得到最终的Promsie对象进行操作。

3.4.ES7 Async/Await

async函数是对Generator函数的改进,在ES7中出现。Generator函数需要依靠执行器才能执行,async函数自带执行器执行方法与常规函数一样。
与Generator函数一样在异步操作的时候async函数返回一个Promise对象,使用then()方法进行后续处理。
使用async实现Generator函数中的样例:

async function readFileStep(path1) {let path2 = await new Promise((resovle, reject) => {fs.readFile(path1, 'utf8', (err, data) => {resovle(data);});});let path3 = await new Promise((resovle, reject) => {fs.readFile(path2, 'utf8', (err, data) => {resovle(data);});});return new Promise((resovle, reject) => {fs.readFile(path3, 'utf8', (err, data) => {resovle(data);});});
}readFileStep('./file1.txt').then((data) => {console.log(data)
});
复制代码

只需要像普通函数一样执行readFileStep即可得到最终结果的Promise对象。

nodejs笔记-异步编程相关推荐

  1. [NodeJS]Node异步编程基础

    零.前言 为什么要用Node? Node把非阻塞IO作为提高应用性能的方式.而在JS中,天生拥有着异步编程机制: 事件机制.同时JS中不存在多进程.这样当你执行相对较慢需要花费时间长的IO操作时并不会 ...

  2. 《CLR via C#》读书笔记-异步编程(一)

    在学习<CLR via C#>的27.2小节中使用了管道.因此先对管道(pipe)的相关知识进行梳理 Pipe 其在System.IO.Pipes命名空间下,此命名空间内的类主要任务就是完 ...

  3. 深入理解nodejs中的异步编程

    文章目录 简介 同步异步和阻塞非阻塞 javascript中的回调 回调函数的错误处理 回调地狱 ES6中的Promise 什么是Promise Promise的特点 Promise的优点 Promi ...

  4. java socket 异步回调函数,分享nodejs异步编程基础之回调函数用法

    nodejs异步编程基础之回调函数用法分析 本文实例讲述了nodejs异步编程基础之回调函数用法.分享给大家供大家参考,具体如下: Node.js 异步编程的直接体现就是回调. 异步编程依托于回调来实 ...

  5. 「C#」异步编程玩法笔记-WinForm中的常见问题

    目录 1.异步更新界面 1.1.问题 1.2.解决问题 1.3.AsyncOperationManager和AsyncOperation 1.4.Invoke.BeginInvoke.EndInvok ...

  6. C# 学习笔记——PL 并行编程 TPL 和传统 .NET 异步编程

    C# 学习笔记--PL 并行编程 TPL 和传统 .NET 异步编程 Task C# 多线程和异步模型 TPL模型 Task,异步,多线程简单总结 1,如何把一个异步封装为Task异步 Task.Fa ...

  7. Java8实战笔记--组合异步编程

    一.Future 初衷是对将来某个时刻会发生的结果进行建模. 想象成这样的场景:你拿了一袋子衣 服到你中意的干洗店去洗.干洗店的员工会给你张发票,告诉你什么时候你的衣服会洗好(这就 是一个Future ...

  8. 《Java8实战》读书笔记10:组合式异步编程 CompletableFuture

    <Java8实战>读书笔记10:组合式异步编程 CompletableFuture 第11章 CompletableFuture:组合式异步编程 11.1 Future 接口 (只是个引子 ...

  9. java 8实战 异步社区_服!看完阿里大牛手写的Java异步编程实战笔记,我惊呆了...

    这份笔记涵盖了Java中常见的异步编程场景,包括单JVM内的异步编程.跨主机通过网络通信的远程过程调用的异步调用与异步处理,以及Web请求的异步处理等. 在讲解Java中每种异步编程技术时都附有案例, ...

最新文章

  1. 高逼格UILabel的闪烁动画效果
  2. 160个Crackme003之4C大法详解
  3. 迁移学习之域自适应理论简介(Domain Adaptation Theory)
  4. SQL2005 数据库数据同步
  5. vue复选框组件自定义对勾_vue+element:树级复选框组件使用
  6. Mr.J--俄罗斯方块实现(框架)
  7. FFmpeg在Linux下安装编译过程
  8. 使用ARKit编写测量应用程序代码:交互和测量
  9. 为你的域名添加子域名(二级域名)并绑定网站
  10. 消息队列返回错误:Resource temporarily unavailable
  11. JPEG 推荐的DC和AC系数的huffman(哈夫曼)码表
  12. 美景订餐管理系统--用于公司内部加班订餐
  13. 子网掩码及其与IP地址、网关的关系
  14. 关于HF-lpt130A与GoKit2.1(stm32)底版的链接通信(持更...)
  15. 聊天的技巧,你是怎样和别人聊天的
  16. obj转stl_STL转STP的方法视频教程,OBJ格式转STP或者IGS开模具格式的过程,STL转STP软件介绍...
  17. python——利用正则表达式爬取豆瓣读书中的图书信息
  18. 设计模式之抽象工厂模式
  19. grbl源码解析——速度前瞻(1)
  20. mysql实践周心得_实践周心得体会

热门文章

  1. mysql中迅速插入百万条测试数据的方法
  2. 自定义Lisp透明命令
  3. TiXml使用详解(转)
  4. 菜鸟学习javascript实例教程
  5. Mocha BSM应用管理——Lotus Domino监控与管理
  6. jenkins-为什么要持续集成
  7. 在Ubuntu kylin 14 64位上flashplayer 插件
  8. Android用户界面开发(11):Menu
  9. nginx反向代理,实现负载均衡
  10. 在SQLMAP中使用动态SQL