这里,我不打算讲Promise的原理,以及回调地狱 blah, blah, blah… 之类。这样的文章网上实在太多了,google 一下一地都是。 但是,讲究竟如何实际去运用的却很少,使用上细节其实很重要。这也是很多人搞不明白这玩意怎么用的原因所在。这里我们以 bluebird 为例看下 Promise 是啥玩意。
首先,Promise它长啥样?
简单来讲,它就是一种把回调转换成链接操作的编程方法。那么什么是链式?jQuery大家都会比较熟悉吧。看下下面这段代码:
$('div').css('highlight').html('Hello world').show();
这就是链式操作。那么,这个链式操作是怎么完成的呢?稍微看下 jQuery 的代码或者写过 jQuery 扩展的人都知道,它是通过在每个 function 体最后返回一个 jQuery 对象完成的。在上面这段代码中,所有的调用之后其实都是一个 jQuery 对象。所有的 css,html, show 都是由 jQuery 对象提供的方法。
jQuery和Promise有什么关系?
jQuery 是链式操作的老祖宗,是不是原创的我不敢说,但是 jQuery 把这种风格推广到整个技术界这点是勿用置疑的。本质上 Promise 也是对这链式操作思想的发扬光大。链式操作的优势是很明显的,对比 jQuery 和以往传统的 javascript 可以明显地看到代码可读性不在一个层级上。我们先看下正常情况下如何读取文件:
fs.readFile('file.txt', "utf-8", function (err, data) {
if (err) return console.error(err);
console.log(data);
});
很常见的。Promise 完成同样的功能是怎么写呢?首先,要初始化生成 promisified function.
var Promise = require('bluebird');
var readAsync = Promise.promisify(fs.readFile);
var readFile = function(path) {
return readAsync(path, 'utf-8');
}
然后是使用:
readFile('/etc/passwd').then(console.log).catch(console.error);
风格上是不是跟 jQuery 很像?但是好像除了短一点,其它好处不明显呐?
fs.readFile('file.txt', 'utf-8', function (err1, data1) {
if (err1) return console.error(err1);
fs.readFile(data1, 'utf-8', function(err2, data2) {
if (err2) return console.error(err2);
console.log(data2);
}
});
readFile('file.txt').then(readFile).then(console.log).catch(console.error);
结果是一样的,可读性不在一个层次上。并且,万一哪个回调中忘了写上 if(err) 这样的判断或者是忘了写 return ,除起错了可不好玩!你会不会写错我不知道,反正我不敢保证我一定不会写错!
上面的代码是怎么回事?
这里,我们确定几个术语:
- Promise是一种编程方法(思想),哲学上的概念。
- bluebird 是对 Promise 的一种实现。
- readFile 是 Promisified Function,它会返加一个 Promise对象。一个 Promise对象就是对一个异步任务的包装,它会产生两种结果:resolved代表成功执行下一个 then, rejected代表失败执行 catch。
- Promise 对象最主要就是提供了 then 方法(catch、finally 都是特殊的 then)。then 是 Promise 的核心,所以 Promise 对象也称作 Thenable。
- then 方法接收一个 function 。在 Promisified Function 顺利完成之后执行这个 function 。出错则执行 catch 的 function
关键的地方在于 then 是怎么运作的?理解 then 也就解决了问题的百分之六十。
- then 接收一个 function 或一个 promise对象(这点要特别注意,后面有讲到一个示例会牵涉到这个关键点)
- then 在异步任务完成后调用这个 function ,同时,会把异步操作的结果值传入,所以上面第一个 then 的 readFile 会接收到 file.txt 的内容。
- then 不管你这个 function 里面返回什么东西,它对返回值只作一个判断:是不是 Promise对象
- 是 Promise对象:把它插入当前链式操作中,后面的 then 会在这个 Promise对象 完成后继续。
- 否 把这个返回值传入到下一个 then 中(当然,如果没有 return ,这个值就是 undefined)。
- 若传入的是promise对象,则会等待该 promise对象执行完成,再继续执行当前的链,之前的结果会被继续传递
总的来讲,then方法执行完毕后总是返回一个 Promise对象。结果是你可以一直 then 下去。。。 好吧,then方法好像很牛逼的样子,那到底怎么牛逼法?
readFile('file.txt').then(function(data) {
if (data == '')
return readFile('file_init.txt').then(readFile); //是 Promise对象
return data; //不是 Promise对象
}).then(console.log).catch(console.error);`
牛逼的地方就在于我们可以动态的修改链式操作的走向。这样的代码看起来既舒服又简单,试一下用传统的方法写一下?
刚才说的示例
doSomething().then(function () {
return doSomethingElse();
});
doSomething().then(function () {
doSomethingElse();
});
doSomething().then(doSomethingElse());
doSomething().then(doSomethingElse);
这是下面四种 Promise 的用法有何差异?, ( 从 阮一峰微博里面挪过来的)。 中修正后的代码。 原文还提到:
doSomething & doSomethingElse both promise object
我想说:
No, they are not. They are promisified function. Function and Object are quite different things.
先别往下拉,请先看下上面四种方式,有什么不同?如果明白了,说明你已经基本掌握 Promise 了。
确认结论是否正确(修正)
- 脱裤子放屁,多了一层 function 包装,跟4是一样的效果。
- 它后面再加 then 是不会等到 doSomethinElse 完成后执行,而是同时进行。
- 传入的其实是一个 promise object,这个 promise object 会被执行并等待其完成,但结果会被忽略,第三个 then 接收到的仍是第一个 readFile 的结果。
- doSomethingElse 本身就是个 function,传入后会被调用,返回一个 Promise对象 ( doSomethingElse 不是 promise object, 它的返回值才是 promise object)。
当然,看不出来也说明不了什么问题,因为这样的示例迷惑性很强,给出的提示也不明显。正常情况下更不会存在这样的代码。顺便一提,这也是函数式语言的 side effect,很容易绕晕掉到坑里。根据 (We have a problem with promises)[http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html]里面所讲,掉坑的人不在少数!
还需要知道其它的吗?
以上只是基础知识,实际应用时当然还有一些扩展的方法需要去了解,比如 spread, all 等。npmjs.org 上的文档那是必读的……
@DevinXian 我开头也说了,原理一google一地的。但具体使用上的细节就较少有讲。有/无 return,return 什么,对链的执行都有影响。《深入浅出NodeJS》我有,Promise 只是略讲了原理,我本人看完是不太明白究竟怎么使用的。所以我这里讲的是使用上的细节,并非原理。