NodeJs 畅谈异步

我们都知道NodeJs是基于事件回调解理事件的,那我们常常会有这样的疑问,比如我们会使用db或redis或fs等模块去存储我们的信息,当我们需要实时返回1个数据并作出处理的时候,异步的通知显得不是那末人性化了,此时我们需要将异步变成同步,在此进程中我们有这样几个知识点要掌握以下,包括JavaScript中的生成器generartor函数,配合它的是yield关键字的暂停,thunk和promise的使用,和co模块和框架thunkify的配合使用。

生成器Generartor

生成器函数是写成:function* functionName(){} 的情势,其本质也是1个函数,所以它具有普通函数所具有的所有特性。除此以外,它还具有以下有用特性:

1. 履行生成器函数后返回1个生成器(Generator),且生成用具有throw()方法,可手动抛出1个异常,也常被用于判断是不是是生成器;

2. 在生成器函数内部可使用yield(或yield*),函数履行到yield的时候都会暂停履行,并返回yield的右值(函数上下文,如变量的绑定等信息会保存),通过生成器的next()方法会返回1个对象,含当前yield右侧表达式的值(value属性),和generator函数是不是已履行完(done属性)等的信息。每次履行next()方法,都会从上次履行的yield的地方往下,直到遇到下1个yield并返回包括相干履行信息的对象后暂停,然后等待下1个next()的履行;

3. 生成器的next()方法返回的是包括yield右侧表达式值及是不是履行终了信息的对象;而next()方法中的参数是上1个暂停处yield的返回值。var a = yield … 该参数的值将赋值给a变量。

实例1:

function test(){
return 'b';
}

function* func(){
var a = yield 'a';

console.log('gen:',a);// gen: undefined
var b = yield test();
console.log('gen:',b);// gen: undefined
}

var func1 = func();
var a = func1.next();
console.log('next:', a);// next: { value: 'a', done: false }
var b = func1.next();
console.log('next:', b);// next: { value: 'b', done: false }
var c = func1.next();
console.log('next:', c);// next: { value: undefined, done: true }

根据上面说过的第3条履行准则:“生成器的next()方法返回的是包括yield右侧表达式值及是不是履行终了信息的对象;而next()方法的参数是上1个暂停处yield的返回值”,由于我们没有往生成器的next()中传入任何值,所以:var a = yield ‘a’;中a的值为undefined。
那我们可以将例子略微修改下:

function test(){
return 'b';
}

function* func(){
var a = yield 'a';
console.log('gen:',a);// gen:1
var b = yield test();
console.log('gen:',b);// gen:2
}
var func2 = func();
var a = func2.next();
console.log('next:', a);// next: { value: 'a', done: false }
var b = func2.next(1);
console.log('next:', b);// next: { value: 'b', done: false }
var c = func2.next(2);
console.log('next:', c);// next: { value: undefined, done: true }

关于yield

普通yield实例:

function* outer() {
yield 'begin';
yield inner();
yield 'end';
}
function* inner() {
yield 'inner';
}
var it = outer(), v;
v = it.next().value;
console.log(v); // -> 输出:begin
v = it.next().value;
console.log(v); // -> 输出:{}
console.log(v.toString()); // -> 输出:[object Generator]
v = it.next().value;
console.log(v); // -> 输出:end

代理yield实例:

function* outer() {
yield 'begin';
/*
* 这行等价于 yield 'inner';就是把inner里面的代码替换过来。同时取得的rt恰好就是inner的返回值
*/
var rt = yield* inner();
console.log(rt); // -> 输出:return from inner
yield 'end';
}
function* inner() {
yield 'inner';
return 'return from inner';
}
var it = outer(), v;
v = it.next().value;
console.log(v); // -> 输出:begin
v = it.next().value;
console.log(v); // -> 输出:inner
v = it.next().value;
console.log(v); // -> 输出:end

根据文档的描写,yield* 后面接受1个 iterable object 作为参数,然后去迭代(iterate)这个迭代器(iterable object),同时 yield* 本身这个表达式的值就是迭代器迭代完成时(done: true)的返回值。调用 generator function 会返回1个 generator object,这个对象本身也是1种 iterable
object,所以,我们可使用 yield* generator_function() 这类写法。
啊,好绕。在我实际的使用进程中,yield* 1般用来在1个 generator 函数里“履行”另外一个 generator 函数,并可以获得其返回值。

关于yield和co

co(function* () {

// yield promise
var a = yield Promise.resolve(1);
console.log(a); // -> 输出:1

// yield thunk
var b = yield later(10);
console.log(b); // -> 输出:10

// yield generator function
var c = yield fn;
console.log(c); // -> 输出:fn_1

// yield generator
var d = yield fn(5);
console.log(d); // -> 输出:fn_5

// yield array
var e = yield [
Promise.resolve('a'),
later('b'),
fn,
fn(5)
];
console.log(e); // -> 输出:['a', 'b', 'fn_1', 'fn_5']

// yield object
var f = yield {
'a': Promise.resolve('a'),
'b': later('b'),
'c': fn,
'd': fn(5)
};
console.log(f); // -> 输出:{a: 'a', b: 'b', c: 'fn_1', d: 'fn_5'}

function* fn(n) {
n = n || 1;
var a = yield later(n);
return 'fn_' + a;
}

function later(n, t) {
t = t || 1000;
return function(done) {
setTimeout(function() { done(null, n); }, t);
};
}

}).catch(function(e) {
console.error(e);
});

通过上面的代码,我们看清了1个事实,那就是co模块中yield后面的只能是promise、generator、thunk函数等,并且他只是同步的,在之前我们讨论过,如果我们想要这句代码 var a = yield ‘value’ 的a变量赋值,那末我们需要在调用next的时候传入参数值,但是在co模块中,他会将promise的通知值(也就是resolve)、thunk函数的灰调函数的值赋值给a变量。

promise函数

Promise对象是CommonJS工作组提出的1种规范,目的是为异步操作提供统1接口。
那末,甚么是Promises?
首先,它是1个对象,也就是说与其他JavaScript对象的用法,没有甚么两样;其次,它起到代理作用(proxy),充当异步操作与回调函数之间的中介。它使得异步操作具有同步操作的接口,使得程序具有正常的同步运行的流程,回调函数没必要再1层层嵌套。
简单说,它的思想是,每个异步任务立刻返回1个Promise对象,由因而立刻返回,所以可以采取同步操作的流程。这个Promises对象有1个then方法,允许指定回调函数,在异步任务完成后调用。
比如,异步操作f1返回1个Promise对象,它的回调函数f2写法以下。(new Promise(f1)).then(f2);

// 传统写法
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// …
});
});
});
});

// Promises的写法
(new Promise(step1))
.then(step2)
.then(step3)
.then(step4);

从上面代码可以看到,采取Promises接口以后,程序流程变得非常清楚,10分易读。
注意,为了便于理解,上面代码的Promise对象的生成格式,做了简化,真实的语法请参照下文。
总的来讲,传统的回调函数写法使得代码混成1团,变得横向发展而不是向下发展。Promises规范就是为了解决这个问题而提出的,目标是使用正常的程序流程(同步),来处理异步操作。它先返回1个Promise对象,后面的操作以同步的方式,寄存在这个对象上面。等到异步操作有了结果,再履行前期存放在它上面的其他操作。
Promises本来只是社区提出的1个构想,1些外部函数库率先实现了这个功能。ECMAScript 6将其写入语言标准,因此目前JavaScript语言原生支持Promise对象。

关于Promise接口,前面说过,Promise接口的基本思想是,异步任务返回1个Promise对象。他只有3种状态pending(未完成)、resolved(已完成)、rejected(失败),那末当它返回时,只有成功和失败两个概念了,履行成功Promise对象传回1个值,状态变成resolved,异步操作失败,Promise对象抛出1个毛病,状态变成rejected。

Promise对象使用then方法添加回调函数。then方法可以接受两个回调函数,第1个是异步操作成功时(变成resolved状态)时的回调函数,第2个是异步操作失败(变成rejected)时的回调函数(可以省略)。1旦状态改变,就调用相应的回调函数。

po.then(console.log, console.error);

上面代码中,Promise对象po使用then方法绑定两个回调函数:操作成功时的回调函数console.log,操作失败时的回调函数console.error(可以省略)。这两个函数都接受异步操作传回的值作为参数。固然了,then可支持链式编程。

上面代码中,po的状态1旦变成resolved,就顺次调用后面每个then指定的回调函数,每步都必须等到前1步完成,才会履行。最后1个then方法的回调函数console.log和console.error,用法上有1点重要的区分。console.log只显示回调函数step3的返回值,而console.error可以显示step1、step2、step3当中任意1个产生的毛病。也就是说,假定step1操作失败,抛出1个毛病,这时候step2和step3都不会再履行了(由于它们是操作成功的回调函数,而不是操作失败的回调函数)。Promises对象开始寻觅,接下来第1个操作失败时的回调函数,在上面代码中是console.error。这就是说,Promises对象的毛病有传递性。

ES6提供了原生的Promise构造函数,用来生成Promise实例。

var promise = new Promise(function(resolve, reject) {
// 异步操作的代码
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});

这里我们可能有点和then的用法冲突了,其实没有,下面我们给出1个ajax配合使用promise的用法:

function search(term) {
var url = 'http://example.com/search?q=' + term;
var xhr = new XMLHttpRequest();
var result;

var p = new Promise(function (resolve, reject) {
xhr.open('GET', url, true);
xhr.onload = function (e) {
if (this.status === 200) {
result = JSON.parse(this.responseText);
resolve(result);
}
};
xhr.onerror = function (e) {
reject(e);
};
xhr.send();
});

return p;
}

search("paramValue").then(console.log, console.error);

此时很明白了吧。。。。。。

那我们说了这么多,我们也想promise配合co模块使用1次,将其异步变成同步的使用方法就能够了,实例以下:

var fs = require('fs');

var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) reject(error);
resolve(data);
});
});
};

var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

到此,明白了吧,这个配合co模块实现了同步的操作了吧

关于promise的更多详细讲授,参照 http://javascript.ruanyifeng.com/advanced/promise.html官方,http://www.ruanyifeng.com/blog/2015/05/co.html阮1峰

这其中有几点要注意的也就是,首先promise是1个将异步金字塔管理成为then的调用,他会立即返回1个状态,当其中1个履行成功后,then去履行下1个异步回调函数,请注意它的3个状态,其中1个api Promise.resolve(value),这个api只是的立刻马上返回1个value给return使用。

thunk函数

co模块可以将我们的生成器自动的去履行,而不需要我们手动进行next履行,但是在co的利用中,为了能像写同步代码那样书写异步代码,比较多的使用方式是使用thunk函数(但不是唯1方式,还可以是:Promise)。比如读取文件内容的1步函数fs.readFile()方法,转化为thunk函数的方式以下:

function readFile(path, encoding){
return function(cb){
fs.readFile(path, encoding, cb);
};
}

thunk函数具有以下两个要素:

1. 有且只有1个参数是callback的函数;

2. callback的第1个参数是error。

其实去看看这两点,在node中的回调函数都是可以写成thunk函数去履行的。使用thunk函数,同时结合co我们就能够像写同步代码那样来写书写异步代码,先来个例子感受下:

var co = require('co'),
fs = require('fs'),
Promise = require('es6-promise').Promise;

function readFile(path, encoding){
return function(cb){ // thunk函数
fs.readFile(path, encoding, cb);
};
}

co(function* (){// 外面不可见,但在co内部其实已转化成了promise.then().then()..链式调用的情势
var a = yield readFile('a.txt', {encoding: 'utf8'});
console.log(a); // a
var b = yield readFile('b.txt', {encoding: 'utf8'});
console.log(b); // b
var c = yield readFile('c.txt', {encoding: 'utf8'});
console.log(c); // c
return yield Promise.resolve(a+b+c);
}).then(function(val){
console.log(val); // abc
}).catch(function(error){
console.log(error);
});

其实,对每次都去自己书写1个thunk函数还是比较麻烦的,有1个框架thunkify可以帮我们轻松实现,修改后的代码以下:

var co = require('co'),
thunkify = require('thunkify'),
fs = require('fs'),
Promise = require('es6-promise').Promise;

var readFile = thunkify(fs.readFile);

co(function* (){// 外面不可见,但在co内部其实已转化成了promise.then().then()..链式调用的情势
var a = yield readFile('a.txt', {encoding: 'utf8'});
console.log(a); // a
var b = yield readFile('b.txt', {encoding: 'utf8'});
console.log(b); // b
var c = yield readFile('c.txt', {encoding: 'utf8'});
console.log(c); // c
return yield Promise.resolve(a+b+c);
}).then(function(val){
console.log(val); // abc
}).catch(function(error){
console.log(error);
});

辨别好thunkify的使用,它可以包括1个异步的具有回调函数的thunk函数,提供给co模块使用。

那好,我们最后来1个NodeJs异步中的总结,首先由于生成器和yield可使程序暂停履行,但是其实不具有将异步函数的返回值返回给var a变量的能力,那末我们加入了co这个模块来使用,co可以将yield后面的不管甚么回调异步的返回值return给变量a使用,但是有1个问题,yield后面如果有多个或更多的异步怎样办,我们就使用thunk和promise将所有的异步串行履行起来,最后加入co模块,完善!再次推荐使用thunk,由于我们大多是情况下是需要1个异步函数的返回值,并且有thunkify框架的支持,而promise也是有1些异常的东西,比如,有时候你在拿去它的返回值的时候,不太容易,为何呢,then后如果写了console.log,由于上1次的值传入了log中,log并没与传出所以失去了结果,比如代码好像是这样的

var promise = new Promise(function(resolve, reject) {
if (true){
//成功时,将2作为返回值,传入下1个then的参数中,如果没有下1个then,会将值村吃在promise._result中
resolve(2);
} else {
reject(error);
}
});
var result = promise._result;





波比源码 – 精品源码模版分享 | www.bobi11.com
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!

波比源码 » NodeJs 畅谈异步

发表评论

Hi, 如果你对这款模板有疑问,可以跟我联系哦!

联系站长
赞助VIP 享更多特权,建议使用 QQ 登录
喜欢我嘛?喜欢就按“ctrl+D”收藏我吧!♡