-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjs_async_test.txt
617 lines (532 loc) · 19.2 KB
/
js_async_test.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
Skip to content
This repository
Search
Pull requests
Issues
Gist
@heroWang
Watch 520
Star 3,977
Fork 1,555 alsotang/node-lessons
Code Issues 33 Pull requests 2 Wiki Pulse Graphs
Branch: master New file Find file History node-lessons/lesson17/
Latest commit f52c8d7 on 28 Nov 2014 @zzz6519003 zzz6519003 Update README.md
..
README.md Update README.md a year ago
package.json Add new lesson--use q to replace callback a year ago
README.md
《使用 promise 替代回调函数》
知识点
理解 Promise 概念,为什么需要 promise
学习 q 的 API,利用 q 来替代回调函数(https://github.com/kriskowal/q )
课程内容
第五课(https://github.com/alsotang/node-lessons/tree/master/lesson5 )讲述了如何使用 async 来控制并发。async 的本质是一个流程控制。其实在异步编程中,还有一个更为经典的模型,叫做 Promise/Deferred 模型。
本节我们就来学习这个模型的代表实现:q
首先,我们思考一个典型的异步编程模型,考虑这样一个题目:读取一个文件,在控制台输出这个文件内容。
var fs = require('fs');
fs.readFile('sample.txt', 'utf8', function (err, data) {
console.log(data);
});
看起来很简单,再进一步: 读取两个文件,在控制台输出这两个文件内容。
var fs = require('fs');
fs.readFile('sample01.txt', 'utf8', function (err, data) {
console.log(data);
fs.readFile('sample02.txt', 'utf8', function (err,data) {
console.log(data);
});
});
要是读取更多的文件呢?
var fs = require('fs');
fs.readFile('sample01.txt', 'utf8', function (err, data) {
fs.readFile('sample02.txt', 'utf8', function (err,data) {
fs.readFile('sample03.txt', 'utf8', function (err, data) {
fs.readFile('sample04.txt', 'utf8', function (err, data) {
});
});
});
});
这段代码就是臭名昭著的邪恶金字塔(Pyramid of Doom)。可以使用async来改善这段代码,但是在本课中我们要用promise/defer来改善它。
promise基本概念
先学习promise的基本概念。
promise只有三种状态,未完成,完成(fulfilled)和失败(rejected)。
promise的状态可以由未完成转换成完成,或者未完成转换成失败。
promise的状态转换只发生一次
promise有一个then方法,then方法可以接受3个函数作为参数。前两个函数对应promise的两种状态fulfilled, rejected的回调函数。第三个函数用于处理进度信息。
promiseSomething().then(function(fulfilled){
//当promise状态变成fulfilled时,调用此函数
},function(rejected){
//当promise状态变成rejected时,调用此函数
},function(progress){
//当返回进度信息时,调用此函数
});
学习一个简单的例子:
var Q = require('q');
var defer = Q.defer();
/**
* 获取初始promise
* @private
*/
function getInitialPromise() {
return defer.promise;
}
/**
* 为promise设置三种状态的回调函数
*/
getInitialPromise().then(function(success){
console.log(success);
},function(error){
console.log(error);
},function(progress){
console.log(progress);
});
defer.notify('in progress');//控制台打印in progress
defer.resolve('resolve'); //控制台打印resolve
defer.reject('reject'); //没有输出。promise的状态只能改变一次
promise的传递
then方法会返回一个promise,在下面这个例子中,我们用outputPromise指向then返回的promise。
var outputPromise = getInputPromise().then(function (fulfilled) {
}, function (rejected) {
});
现在outputPromise就变成了受 function(fulfilled) 或者 function(rejected)控制状态的promise了。怎么理解这句话呢?
当function(fulfilled)或者function(rejected)返回一个值,比如一个字符串,数组,对象等等,那么outputPromise的状态就会变成fulfilled。
在下面这个例子中,我们可以看到,当我们把inputPromise的状态通过defer.resovle()变成fulfilled时,控制台输出fulfilled.
当我们把inputPromise的状态通过defer.reject()变成rejected,控制台输出rejected
var Q = require('q');
var defer = Q.defer();
/**
* 通过defer获得promise
* @private
*/
function getInputPromise() {
return defer.promise;
}
/**
* 当inputPromise状态由未完成变成fulfil时,调用function(fulfilled)
* 当inputPromise状态由未完成变成rejected时,调用function(rejected)
* 将then返回的promise赋给outputPromise
* function(fulfilled) 和 function(rejected) 通过返回字符串将outputPromise的状态由
* 未完成改变为fulfilled
* @private
*/
var outputPromise = getInputPromise().then(function(fulfilled){
return 'fulfilled';
},function(rejected){
return 'rejected';
});
/**
* 当outputPromise状态由未完成变成fulfil时,调用function(fulfilled),控制台打印'fulfilled: fulfilled'。
* 当outputPromise状态由未完成变成rejected, 调用function(rejected), 控制台打印'fulfilled: rejected'。
*/
outputPromise.then(function(fulfilled){
console.log('fulfilled: ' + fulfilled);
},function(rejected){
console.log('rejected: ' + rejected);
});
/**
* 将inputPromise的状态由未完成变成rejected
*/
defer.reject(); //输出 fulfilled: rejected
/**
* 将inputPromise的状态由未完成变成fulfilled
*/
//defer.resolve(); //输出 fulfilled: fulfilled
当function(fulfilled)或者function(rejected)抛出异常时,那么outputPromise的状态就会变成rejected
var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**
* 通过defer获得promise
* @private
*/
function getInputPromise() {
return defer.promise;
}
/**
* 当inputPromise状态由未完成变成fulfil时,调用function(fulfilled)
* 当inputPromise状态由未完成变成rejected时,调用function(rejected)
* 将then返回的promise赋给outputPromise
* function(fulfilled) 和 function(rejected) 通过抛出异常将outputPromise的状态由
* 未完成改变为reject
* @private
*/
var outputPromise = getInputPromise().then(function(fulfilled){
throw new Error('fulfilled');
},function(rejected){
throw new Error('rejected');
});
/**
* 当outputPromise状态由未完成变成fulfil时,调用function(fulfilled)。
* 当outputPromise状态由未完成变成rejected, 调用function(rejected)。
*/
outputPromise.then(function(fulfilled){
console.log('fulfilled: ' + fulfilled);
},function(rejected){
console.log('rejected: ' + rejected);
});
/**
* 将inputPromise的状态由未完成变成rejected
*/
defer.reject(); //控制台打印 rejected [Error:rejected]
/**
* 将inputPromise的状态由未完成变成fulfilled
*/
//defer.resolve(); //控制台打印 rejected [Error:fulfilled]
当function(fulfilled)或者function(rejected)返回一个promise时,outputPromise就会成为这个新的promise.
这样做有什么意义呢? 主要在于聚合结果(Q.all),管理延时,异常恢复等等
比如说我们想要读取一个文件的内容,然后把这些内容打印出来。可能会写出这样的代码:
//错误的写法
var outputPromise = getInputPromise().then(function(fulfilled){
fs.readFile('test.txt','utf8',function(err,data){
return data;
});
});
然而这样写是错误的,因为function(fulfilled)并没有返回任何值。需要下面的方式:
var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**
* 通过defer获得promise
* @private
*/
function getInputPromise() {
return defer.promise;
}
/**
* 当inputPromise状态由未完成变成fulfil时,调用function(fulfilled)
* 当inputPromise状态由未完成变成rejected时,调用function(rejected)
* 将then返回的promise赋给outputPromise
* function(fulfilled)将新的promise赋给outputPromise
* 未完成改变为reject
* @private
*/
var outputPromise = getInputPromise().then(function(fulfilled){
var myDefer = Q.defer();
fs.readFile('test.txt','utf8',function(err,data){
if(!err && data) {
myDefer.resolve(data);
}
});
return myDefer.promise;
},function(rejected){
throw new Error('rejected');
});
/**
* 当outputPromise状态由未完成变成fulfil时,调用function(fulfilled),控制台打印test.txt文件内容。
*
*/
outputPromise.then(function(fulfilled){
console.log(fulfilled);
},function(rejected){
console.log(rejected);
});
/**
* 将inputPromise的状态由未完成变成rejected
*/
//defer.reject();
/**
* 将inputPromise的状态由未完成变成fulfilled
*/
defer.resolve(); //控制台打印出 test.txt 的内容
方法传递
方法传递有些类似于Java中的try和catch。当一个异常没有响应的捕获时,这个异常会接着往下传递。
方法传递的含义是当一个状态没有响应的回调函数,就会沿着then往下找。
没有提供function(rejected)
var outputPromise = getInputPromise().then(function(fulfilled){})
如果inputPromise的状态由未完成变成rejected, 此时对rejected的处理会由outputPromise来完成。
var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**
* 通过defer获得promise
* @private
*/
function getInputPromise() {
return defer.promise;
}
/**
* 当inputPromise状态由未完成变成fulfil时,调用function(fulfilled)
* 当inputPromise状态由未完成变成rejected时,这个rejected会传向outputPromise
*/
var outputPromise = getInputPromise().then(function(fulfilled){
return 'fulfilled'
});
outputPromise.then(function(fulfilled){
console.log('fulfilled: ' + fulfilled);
},function(rejected){
console.log('rejected: ' + rejected);
});
/**
* 将inputPromise的状态由未完成变成rejected
*/
defer.reject('inputpromise rejected'); //控制台打印rejected: inputpromise rejected
/**
* 将inputPromise的状态由未完成变成fulfilled
*/
//defer.resolve();
没有提供function(fulfilled)
var outputPromise = getInputPromise().then(null,function(rejected){})
如果inputPromise的状态由未完成变成fulfilled, 此时对fulfil的处理会由outputPromise来完成。
var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**
* 通过defer获得promise
* @private
*/
function getInputPromise() {
return defer.promise;
}
/**
* 当inputPromise状态由未完成变成fulfil时,传递给outputPromise
* 当inputPromise状态由未完成变成rejected时,调用function(rejected)
* function(fulfilled)将新的promise赋给outputPromise
* 未完成改变为reject
* @private
*/
var outputPromise = getInputPromise().then(null,function(rejected){
return 'rejected';
});
outputPromise.then(function(fulfilled){
console.log('fulfilled: ' + fulfilled);
},function(rejected){
console.log('rejected: ' + rejected);
});
/**
* 将inputPromise的状态由未完成变成rejected
*/
//defer.reject('inputpromise rejected');
/**
* 将inputPromise的状态由未完成变成fulfilled
*/
defer.resolve('inputpromise fulfilled'); //控制台打印fulfilled: inputpromise fulfilled
可以使用fail(function(error))来专门针对错误处理,而不是使用then(null,function(error))
var outputPromise = getInputPromise().fail(function(error){})
看这个例子
var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**
* 通过defer获得promise
* @private
*/
function getInputPromise() {
return defer.promise;
}
/**
* 当inputPromise状态由未完成变成fulfil时,调用then(function(fulfilled))
* 当inputPromise状态由未完成变成rejected时,调用fail(function(error))
* function(fulfilled)将新的promise赋给outputPromise
* 未完成改变为reject
* @private
*/
var outputPromise = getInputPromise().then(function(fulfilled){
return fulfilled;
}).fail(function(error){
console.log('fail: ' + error);
});
/**
* 将inputPromise的状态由未完成变成rejected
*/
defer.reject('inputpromise rejected');//控制台打印fail: inputpromise rejected
/**
* 将inputPromise的状态由未完成变成fulfilled
*/
//defer.resolve('inputpromise fulfilled');
可以使用progress(function(progress))来专门针对进度信息进行处理,而不是使用 then(function(success){},function(error){},function(progress){})
var Q = require('q');
var defer = Q.defer();
/**
* 获取初始promise
* @private
*/
function getInitialPromise() {
return defer.promise;
}
/**
* 为promise设置progress信息处理函数
*/
var outputPromise = getInitialPromise().then(function(success){
}).progress(function(progress){
console.log(progress);
});
defer.notify(1);
defer.notify(2); //控制台打印1,2
promise链
promise链提供了一种让函数顺序执行的方法。
函数顺序执行是很重要的一个功能。比如知道用户名,需要根据用户名从数据库中找到相应的用户,然后将用户信息传给下一个函数进行处理。
var Q = require('q');
var defer = Q.defer();
//一个模拟数据库
var users = [{'name':'andrew','passwd':'password'}];
function getUsername() {
return defer.promise;
}
function getUser(username){
var user;
users.forEach(function(element){
if(element.name === username) {
user = element;
}
});
return user;
}
//promise链
getUsername().then(function(username){
return getUser(username);
}).then(function(user){
console.log(user);
});
defer.resolve('andrew');
我们通过两个then达到让函数顺序执行的目的。
then的数量其实是没有限制的。当然,then的数量过多,要手动把他们链接起来是很麻烦的。比如
foo(initialVal).then(bar).then(baz).then(qux)
这时我们需要用代码来动态制造promise链
var funcs = [foo,bar,baz,qux]
var result = Q(initialVal)
funcs.forEach(function(func){
result = result.then(func)
})
return result
当然,我们可以再简洁一点
var funcs = [foo,bar,baz,qux]
funcs.reduce(function(pre,current),Q(initialVal){
return pre.then(current)
})
看一个具体的例子
function foo(result) {
console.log(result);
return result+result;
}
//手动链接
Q('hello').then(foo).then(foo).then(foo); //控制台输出: hello
// hellohello
// hellohellohello
//动态链接
var funcs = [foo,foo,foo];
var result = Q('hello');
funcs.forEach(function(func){
result = result.then(func);
});
//精简后的动态链接
funcs.reduce(function(prev,current){
return prev.then(current);
},Q('hello'));
对于promise链,最重要的是需要理解为什么这个链能够顺序执行。如果能够理解这点,那么以后自己写promise链可以说是轻车熟路啊。
promise组合
回到我们一开始读取文件内容的例子。如果现在让我们把它改写成promise链,是不是很简单呢?
var Q = require('q'),
fs = require('fs');
function printFileContent(fileName) {
return function(){
var defer = Q.defer();
fs.readFile(fileName,'utf8',function(err,data){
if(!err && data) {
console.log(data);
defer.resolve();
}
})
return defer.promise;
}
}
//手动链接
printFileContent('sample01.txt')()
.then(printFileContent('sample02.txt'))
.then(printFileContent('sample03.txt'))
.then(printFileContent('sample04.txt')); //控制台顺序打印sample01到sample04的内容
很有成就感是不是。然而如果仔细分析,我们会发现为什么要他们顺序执行呢,如果他们能够并行执行不是更好吗? 我们只需要在他们都执行完成之后,得到他们的执行结果就可以了。
我们可以通过Q.all([promise1,promise2...])将多个promise组合成一个promise返回。 注意:
当all里面所有的promise都fulfil时,Q.all返回的promise状态变成fulfil
当任意一个promise被reject时,Q.all返回的promise状态立即变成reject
我们来把上面读取文件内容的例子改成并行执行吧
var Q = require('q');
var fs = require('fs');
/**
*读取文件内容
*@private
*/
function printFileContent(fileName) {
//Todo: 这段代码不够简洁。可以使用Q.denodeify来简化
var defer = Q.defer();
fs.readFile(fileName,'utf8',function(err,data){
if(!err && data) {
console.log(data);
defer.resolve(fileName + ' success ');
}else {
defer.reject(fileName + ' fail ');
}
})
return defer.promise;
}
Q.all([printFileContent('sample01.txt'),printFileContent('sample02.txt'),printFileContent('sample03.txt'),printFileContent('sample04.txt')])
.then(function(success){
console.log(success);
}); //控制台打印各个文件内容 顺序不一定
现在知道Q.all会在任意一个promise进入reject状态后立即进入reject状态。如果我们需要等到所有的promise都发生状态后(有的fulfil, 有的reject),再转换Q.all的状态, 这时我们可以使用Q.allSettled
var Q = require('q'),
fs = require('fs');
/**
*读取文件内容
*@private
*/
function printFileContent(fileName) {
//Todo: 这段代码不够简洁。可以使用Q.denodeify来简化
var defer = Q.defer();
fs.readFile(fileName,'utf8',function(err,data){
if(!err && data) {
console.log(data);
defer.resolve(fileName + ' success ');
}else {
defer.reject(fileName + ' fail ');
}
})
return defer.promise;
}
Q.allSettled([printFileContent('nosuchfile.txt'),printFileContent('sample02.txt'),printFileContent('sample03.txt'),printFileContent('sample04.txt')])
.then(function(results){
results.forEach(
function(result) {
console.log(result.state);
}
);
});
结束promise链
通常,对于一个promise链,有两种结束的方式。第一种方式是返回最后一个promise
如 return foo().then(bar);
第二种方式就是通过done来结束promise链
如 foo().then(bar).done()
为什么需要通过done来结束一个promise链呢? 如果在我们的链中有错误没有被处理,那么在一个正确结束的promise链中,这个没被处理的错误会通过异常抛出。
var Q = require('q');
/**
*@private
*/
function getPromise(msg,timeout,opt) {
var defer = Q.defer();
setTimeout(function(){
console.log(msg);
if(opt)
defer.reject(msg);
else
defer.resolve(msg);
},timeout);
return defer.promise;
}
/**
*没有用done()结束的promise链
*由于getPromse('2',2000,'opt')返回rejected, getPromise('3',1000)就没有执行
*然后这个异常并没有任何提醒,是一个潜在的bug
*/
getPromise('1',3000)
.then(function(){return getPromise('2',2000,'opt')})
.then(function(){return getPromise('3',1000)});
/**
*用done()结束的promise链
*有异常抛出
*/
getPromise('1',3000)
.then(function(){return getPromise('2',2000,'opt')})
.then(function(){return getPromise('3',1000)})
.done();
结束语
当你理解完上面所有的知识点时,你就会正确高效的使用promise了。本节只是讲了promise的原理和几个基本的API,不过你掌握了这些之后,再去看q的文档,应该很容易就能理解各个api的意图。
Status API Training Shop Blog About Pricing
© 2015 GitHub, Inc. Terms Privacy Security Contact Help