《使用 promise 替代回调函数》

    1. 理解 Promise 概念,为什么需要 promise
    2. 学习 q 的 API,利用 q 来替代回调函数( )

    课程内容

    本节我们就来学习这个模型的代表实现:q

    首先,我们思考一个典型的异步编程模型,考虑这样一个题目:读取一个文件,在控制台输出这个文件内容。

    看起来很简单,再进一步: 读取两个文件,在控制台输出这两个文件内容。

    1. fs.readFile('sample01.txt', 'utf8', function (err, data) {
    2. console.log(data);
    3. fs.readFile('sample02.txt', 'utf8', function (err,data) {
    4. console.log(data);
    5. });
    6. });

    要是读取更多的文件呢?

    1. var fs = require('fs');
    2. fs.readFile('sample01.txt', 'utf8', function (err, data) {
    3. fs.readFile('sample02.txt', 'utf8', function (err,data) {
    4. fs.readFile('sample03.txt', 'utf8', function (err, data) {
    5. fs.readFile('sample04.txt', 'utf8', function (err, data) {
    6. });
    7. });
    8. });
    9. });

    这段代码就是臭名昭著的邪恶金字塔(Pyramid of Doom)。可以使用async来改善这段代码,但是在本课中我们要用promise/defer来改善它。

    promise基本概念

    先学习promise的基本概念。

    • promise只有三种状态,未完成,完成(fulfilled)和失败(rejected)。
    • promise的状态可以由未完成转换成完成,或者未完成转换成失败。
    • promise的状态转换只发生一次

    promise有一个then方法,then方法可以接受3个函数作为参数。前两个函数对应promise的两种状态fulfilled, rejected的回调函数。第三个函数用于处理进度信息。

    1. promiseSomething().then(function(fulfilled){
    2. //当promise状态变成fulfilled时,调用此函数
    3. },function(rejected){
    4. //当promise状态变成rejected时,调用此函数
    5. },function(progress){
    6. //当返回进度信息时,调用此函数
    7. });

    学习一个简单的例子:

    1. var Q = require('q');
    2. var defer = Q.defer();
    3. /**
    4. * 获取初始promise
    5. * @private
    6. */
    7. function getInitialPromise() {
    8. return defer.promise;
    9. }
    10. /**
    11. * 为promise设置三种状态的回调函数
    12. */
    13. getInitialPromise().then(function(success){
    14. console.log(success);
    15. },function(error){
    16. console.log(error);
    17. },function(progress){
    18. console.log(progress);
    19. });
    20. defer.notify('in progress');//控制台打印in progress
    21. defer.resolve('resolve'); //控制台打印resolve
    22. defer.reject('reject'); //没有输出。promise的状态只能改变一次

    then方法会返回一个promise,在下面这个例子中,我们用outputPromise指向then返回的promise。

    1. var outputPromise = getInputPromise().then(function (fulfilled) {
    2. }, function (rejected) {
    3. });

    现在outputPromise就变成了受 function(fulfilled) 或者 function(rejected)控制状态的promise了。怎么理解这句话呢?

    • 当function(fulfilled)或者function(rejected)返回一个值,比如一个字符串,数组,对象等等,那么outputPromise的状态就会变成fulfilled。

    在下面这个例子中,我们可以看到,当我们把inputPromise的状态通过defer.resovle()变成fulfilled时,控制台输出fulfilled.

    当我们把inputPromise的状态通过defer.reject()变成rejected,控制台输出rejected

    1. var Q = require('q');
    2. var defer = Q.defer();
    3. /**
    4. * 通过defer获得promise
    5. * @private
    6. */
    7. function getInputPromise() {
    8. return defer.promise;
    9. }
    10. /**
    11. * 当inputPromise状态由未完成变成fulfil时,调用function(fulfilled)
    12. * 当inputPromise状态由未完成变成rejected时,调用function(rejected)
    13. * 将then返回的promise赋给outputPromise
    14. * function(fulfilled) 和 function(rejected) 通过返回字符串将outputPromise的状态由
    15. * 未完成改变为fulfilled
    16. * @private
    17. */
    18. var outputPromise = getInputPromise().then(function(fulfilled){
    19. return 'fulfilled';
    20. },function(rejected){
    21. return 'rejected';
    22. });
    23. /**
    24. * 当outputPromise状态由未完成变成fulfil时,调用function(fulfilled),控制台打印'fulfilled: fulfilled'。
    25. * 当outputPromise状态由未完成变成rejected, 调用function(rejected), 控制台打印'rejected: rejected'。
    26. */
    27. outputPromise.then(function(fulfilled){
    28. console.log('fulfilled: ' + fulfilled);
    29. },function(rejected){
    30. console.log('rejected: ' + rejected);
    31. });
    32. /**
    33. * 将inputPromise的状态由未完成变成rejected
    34. */
    35. defer.reject(); //输出 fulfilled: rejected
    36. /**
    37. * 将inputPromise的状态由未完成变成fulfilled
    38. */
    39. //defer.resolve(); //输出 fulfilled: fulfilled
    • 当function(fulfilled)或者function(rejected)抛出异常时,那么outputPromise的状态就会变成rejected
    1. var Q = require('q');
    2. var fs = require('fs');
    3. var defer = Q.defer();
    4. /**
    5. * 通过defer获得promise
    6. * @private
    7. */
    8. function getInputPromise() {
    9. return defer.promise;
    10. }
    11. /**
    12. * 当inputPromise状态由未完成变成fulfil时,调用function(fulfilled)
    13. * 当inputPromise状态由未完成变成rejected时,调用function(rejected)
    14. * 将then返回的promise赋给outputPromise
    15. * function(fulfilled) 和 function(rejected) 通过抛出异常将outputPromise的状态由
    16. * 未完成改变为reject
    17. * @private
    18. */
    19. var outputPromise = getInputPromise().then(function(fulfilled){
    20. throw new Error('fulfilled');
    21. },function(rejected){
    22. throw new Error('rejected');
    23. });
    24. /**
    25. * 当outputPromise状态由未完成变成fulfil时,调用function(fulfilled)。
    26. * 当outputPromise状态由未完成变成rejected, 调用function(rejected)。
    27. */
    28. outputPromise.then(function(fulfilled){
    29. console.log('fulfilled: ' + fulfilled);
    30. },function(rejected){
    31. console.log('rejected: ' + rejected);
    32. });
    33. /**
    34. * 将inputPromise的状态由未完成变成rejected
    35. */
    36. defer.reject(); //控制台打印 rejected [Error:rejected]
    37. * 将inputPromise的状态由未完成变成fulfilled
    38. */
    39. //defer.resolve(); //控制台打印 rejected [Error:fulfilled]
    • 当function(fulfilled)或者function(rejected)返回一个promise时,outputPromise就会成为这个新的promise.

    比如说我们想要读取一个文件的内容,然后把这些内容打印出来。可能会写出这样的代码:

    然而这样写是错误的,因为function(fulfilled)并没有返回任何值。需要下面的方式:

    1. var Q = require('q');
    2. var fs = require('fs');
    3. var defer = Q.defer();
    4. /**
    5. * @private
    6. */
    7. function getInputPromise() {
    8. return defer.promise;
    9. }
    10. /**
    11. * 当inputPromise状态由未完成变成fulfil时,调用function(fulfilled)
    12. * 当inputPromise状态由未完成变成rejected时,调用function(rejected)
    13. * 将then返回的promise赋给outputPromise
    14. * function(fulfilled)将新的promise赋给outputPromise
    15. * 未完成改变为reject
    16. * @private
    17. */
    18. var outputPromise = getInputPromise().then(function(fulfilled){
    19. var myDefer = Q.defer();
    20. fs.readFile('test.txt','utf8',function(err,data){
    21. if(!err && data) {
    22. myDefer.resolve(data);
    23. }
    24. });
    25. return myDefer.promise;
    26. },function(rejected){
    27. throw new Error('rejected');
    28. });
    29. /**
    30. * 当outputPromise状态由未完成变成fulfil时,调用function(fulfilled),控制台打印test.txt文件内容。
    31. *
    32. */
    33. outputPromise.then(function(fulfilled){
    34. console.log(fulfilled);
    35. },function(rejected){
    36. console.log(rejected);
    37. });
    38. /**
    39. * 将inputPromise的状态由未完成变成rejected
    40. */
    41. //defer.reject();
    42. /**
    43. * 将inputPromise的状态由未完成变成fulfilled
    44. */
    45. defer.resolve(); //控制台打印出 test.txt 的内容

    方法传递

    方法传递有些类似于Java中的try和catch。当一个异常没有响应的捕获时,这个异常会接着往下传递。

    方法传递的含义是当一个状态没有响应的回调函数,就会沿着then往下找。

    • 没有提供function(rejected)
    1. var outputPromise = getInputPromise().then(function(fulfilled){})

    如果inputPromise的状态由未完成变成rejected, 此时对rejected的处理会由outputPromise来完成。

    1. var Q = require('q');
    2. var fs = require('fs');
    3. var defer = Q.defer();
    4. /**
    5. * 通过defer获得promise
    6. * @private
    7. */
    8. function getInputPromise() {
    9. return defer.promise;
    10. }
    11. /**
    12. * 当inputPromise状态由未完成变成fulfil时,调用function(fulfilled)
    13. * 当inputPromise状态由未完成变成rejected时,这个rejected会传向outputPromise
    14. */
    15. var outputPromise = getInputPromise().then(function(fulfilled){
    16. return 'fulfilled'
    17. });
    18. outputPromise.then(function(fulfilled){
    19. console.log('fulfilled: ' + fulfilled);
    20. },function(rejected){
    21. console.log('rejected: ' + rejected);
    22. });
    23. /**
    24. * 将inputPromise的状态由未完成变成rejected
    25. */
    26. defer.reject('inputpromise rejected'); //控制台打印rejected: inputpromise rejected
    27. /**
    28. * 将inputPromise的状态由未完成变成fulfilled
    29. */
    30. //defer.resolve();
    • 没有提供function(fulfilled)
    1. var outputPromise = getInputPromise().then(null,function(rejected){})

    如果inputPromise的状态由未完成变成fulfilled, 此时对fulfil的处理会由outputPromise来完成。

    1. var Q = require('q');
    2. var fs = require('fs');
    3. var defer = Q.defer();
    4. /**
    5. * 通过defer获得promise
    6. * @private
    7. */
    8. function getInputPromise() {
    9. return defer.promise;
    10. }
    11. /**
    12. * 当inputPromise状态由未完成变成fulfil时,传递给outputPromise
    13. * 当inputPromise状态由未完成变成rejected时,调用function(rejected)
    14. * function(fulfilled)将新的promise赋给outputPromise
    15. * 未完成改变为reject
    16. * @private
    17. */
    18. var outputPromise = getInputPromise().then(null,function(rejected){
    19. return 'rejected';
    20. });
    21. outputPromise.then(function(fulfilled){
    22. console.log('fulfilled: ' + fulfilled);
    23. },function(rejected){
    24. console.log('rejected: ' + rejected);
    25. });
    26. /**
    27. * 将inputPromise的状态由未完成变成rejected
    28. */
    29. //defer.reject('inputpromise rejected');
    30. /**
    31. * 将inputPromise的状态由未完成变成fulfilled
    32. */
    33. defer.resolve('inputpromise fulfilled'); //控制台打印fulfilled: inputpromise fulfilled
    • 可以使用fail(function(error))来专门针对错误处理,而不是使用then(null,function(error))
    1. var outputPromise = getInputPromise().fail(function(error){})

    看这个例子

    1. var Q = require('q');
    2. var fs = require('fs');
    3. var defer = Q.defer();
    4. /**
    5. * 通过defer获得promise
    6. * @private
    7. */
    8. function getInputPromise() {
    9. return defer.promise;
    10. }
    11. /**
    12. * 当inputPromise状态由未完成变成fulfil时,调用then(function(fulfilled))
    13. * 当inputPromise状态由未完成变成rejected时,调用fail(function(error))
    14. * function(fulfilled)将新的promise赋给outputPromise
    15. * @private
    16. */
    17. var outputPromise = getInputPromise().then(function(fulfilled){
    18. return fulfilled;
    19. }).fail(function(error){
    20. console.log('fail: ' + error);
    21. /**
    22. * 将inputPromise的状态由未完成变成rejected
    23. */
    24. defer.reject('inputpromise rejected');//控制台打印fail: inputpromise rejected
    25. /**
    26. * 将inputPromise的状态由未完成变成fulfilled
    27. */
    28. //defer.resolve('inputpromise fulfilled');
    • 可以使用progress(function(progress))来专门针对进度信息进行处理,而不是使用 then(function(success){},function(error){},function(progress){})

    promise链

    promise链提供了一种让函数顺序执行的方法。

    函数顺序执行是很重要的一个功能。比如知道用户名,需要根据用户名从数据库中找到相应的用户,然后将用户信息传给下一个函数进行处理。

    1. var Q = require('q');
    2. var defer = Q.defer();
    3. //一个模拟数据库
    4. var users = [{'name':'andrew','passwd':'password'}];
    5. function getUsername() {
    6. return defer.promise;
    7. }
    8. function getUser(username){
    9. var user;
    10. users.forEach(function(element){
    11. if(element.name === username) {
    12. user = element;
    13. }
    14. });
    15. return user;
    16. }
    17. //promise链
    18. getUsername().then(function(username){
    19. return getUser(username);
    20. }).then(function(user){
    21. console.log(user);
    22. });
    23. defer.resolve('andrew');

    我们通过两个then达到让函数顺序执行的目的。

    then的数量其实是没有限制的。当然,then的数量过多,要手动把他们链接起来是很麻烦的。比如

    1. foo(initialVal).then(bar).then(baz).then(qux)

    这时我们需要用代码来动态制造promise链

    1. var funcs = [foo,bar,baz,qux]
    2. var result = Q(initialVal)
    3. funcs.forEach(function(func){
    4. result = result.then(func)
    5. })
    6. return result
    1. var funcs = [foo,bar,baz,qux]
    2. funcs.reduce(function(pre,current),Q(initialVal){
    3. return pre.then(current)
    4. })

    看一个具体的例子

    1. function foo(result) {
    2. console.log(result);
    3. return result+result;
    4. }
    5. //手动链接
    6. Q('hello').then(foo).then(foo).then(foo); //控制台输出: hello
    7. // hellohello
    8. // hellohellohello
    9. //动态链接
    10. var funcs = [foo,foo,foo];
    11. var result = Q('hello');
    12. funcs.forEach(function(func){
    13. result = result.then(func);
    14. });
    15. //精简后的动态链接
    16. funcs.reduce(function(prev,current){
    17. return prev.then(current);
    18. },Q('hello'));

    对于promise链,最重要的是需要理解为什么这个链能够顺序执行。如果能够理解这点,那么以后自己写promise链可以说是轻车熟路啊。

    回到我们一开始读取文件内容的例子。如果现在让我们把它改写成promise链,是不是很简单呢?

    1. var Q = require('q'),
    2. fs = require('fs');
    3. function printFileContent(fileName) {
    4. return function(){
    5. var defer = Q.defer();
    6. fs.readFile(fileName,'utf8',function(err,data){
    7. if(!err && data) {
    8. console.log(data);
    9. defer.resolve();
    10. }
    11. })
    12. return defer.promise;
    13. }
    14. }
    15. //手动链接
    16. printFileContent('sample01.txt')()
    17. .then(printFileContent('sample02.txt'))
    18. .then(printFileContent('sample03.txt'))
    19. .then(printFileContent('sample04.txt')); //控制台顺序打印sample01到sample04的内容

    很有成就感是不是。然而如果仔细分析,我们会发现为什么要他们顺序执行呢,如果他们能够并行执行不是更好吗? 我们只需要在他们都执行完成之后,得到他们的执行结果就可以了。

    我们可以通过Q.all([promise1,promise2…])将多个promise组合成一个promise返回。
    注意:

    1. 当all里面所有的promise都fulfil时,Q.all返回的promise状态变成fulfil
    2. 当任意一个promise被reject时,Q.all返回的promise状态立即变成reject

    我们来把上面读取文件内容的例子改成并行执行吧

    1. var Q = require('q');
    2. var fs = require('fs');
    3. /**
    4. *读取文件内容
    5. *@private
    6. */
    7. function printFileContent(fileName) {
    8. //Todo: 这段代码不够简洁。可以使用Q.denodeify来简化
    9. var defer = Q.defer();
    10. fs.readFile(fileName,'utf8',function(err,data){
    11. if(!err && data) {
    12. console.log(data);
    13. defer.resolve(fileName + ' success ');
    14. }else {
    15. defer.reject(fileName + ' fail ');
    16. }
    17. })
    18. return defer.promise;
    19. }
    20. Q.all([printFileContent('sample01.txt'),printFileContent('sample02.txt'),printFileContent('sample03.txt'),printFileContent('sample04.txt')])
    21. .then(function(success){
    22. console.log(success);
    23. }); //控制台打印各个文件内容 顺序不一定

    现在知道Q.all会在任意一个promise进入reject状态后立即进入reject状态。如果我们需要等到所有的promise都发生状态后(有的fulfil, 有的reject),再转换Q.all的状态, 这时我们可以使用Q.allSettled

    结束promise链

    通常,对于一个promise链,有两种结束的方式。第一种方式是返回最后一个promise

    return foo().then(bar);

    第二种方式就是通过done来结束promise链

    foo().then(bar).done()

    为什么需要通过done来结束一个promise链呢? 如果在我们的链中有错误没有被处理,那么在一个正确结束的promise链中,这个没被处理的错误会通过异常抛出。

    1. var Q = require('q');
    2. /**
    3. *@private
    4. */
    5. function getPromise(msg,timeout,opt) {
    6. var defer = Q.defer();
    7. setTimeout(function(){
    8. console.log(msg);
    9. if(opt)
    10. defer.reject(msg);
    11. else
    12. defer.resolve(msg);
    13. },timeout);
    14. return defer.promise;
    15. }
    16. /**
    17. *没有用done()结束的promise链
    18. *由于getPromse('2',2000,'opt')返回rejected, getPromise('3',1000)就没有执行
    19. *然后这个异常并没有任何提醒,是一个潜在的bug
    20. */
    21. getPromise('1',3000)
    22. .then(function(){return getPromise('2',2000,'opt')})
    23. .then(function(){return getPromise('3',1000)});
    24. /**
    25. *用done()结束的promise链
    26. *有异常抛出
    27. */
    28. getPromise('1',3000)
    29. .then(function(){return getPromise('2',2000,'opt')})

    结束语