原文翻译:https://www.promisejs.org/
个人的理解:原文就叫做Promise,我之所以改成Promise思想,一方面是因为他是首页,单纯叫Promise有点不清楚;另一方面,从全文看下来,虽然文章有点过时,但是关于为什么会有Promise还是讲的有理有据的。
动机
假设你利用下面这段代码去读取文件,并将文件解析成json格式。下面代码看起来非常简洁,便于理解。但是因为这段代码会阻塞后面运行,因此你很少会用它。也就是说,当你从硬盘读文件时,其他的事情将都会暂停。1
2
3function readJSONSync(filename) {
return JSON.parse(fs.readFileSync(filename, 'utf8'));
}
为了提高性能且让应用有响应式,我们需要将所有关于IO的操作都转化成异步操作。最简单的方式是利用回调函数。但是单纯地用回调函数也是有问题的:1
2
3
4
5
6function readJSON(filename, callback){
fs.readFile(filename, 'utf8', function (err, res){
if (err) return callback(err);
callback(null, JSON.parse(res));
});
}
- callback这个回调函数参数会令我们感到困惑,同时这个回调函数输入的是什么,返回的是什么值,我们也是不知道的。
- 不符合控制流的思想,即并不是主动控制流程。
- 无法处理
JSON.parse
的异常。
我们需要处理JSON.parse
的异常。但是我们还要注意,不要处理回调函数(callback)里的异常(PS:回调函数里的报错应该是由回调函数处理)。因此,我们写下了下面一段糟糕的代码去处理报错:1
2
3
4
5
6
7
8
9
10
11function readJSON(filename, callback){
fs.readFile(filename, 'utf8', function (err, res){
if (err) return callback(err);
try {
res = JSON.parse(res);
} catch (ex) {
return callback(ex);
}
callback(null, res);
});
}
尽管我们用上面那段代码处理了异常,但是仍然未解决callback这个问题。Promise可以不用考虑callback参数问题,就能很简洁地处理异常。并且,不需要改变底层架构,即代码侵入不强。(比方:https://www.promisejs.org/implementing/)
什么是Promise?
Promise的核心是代表一个异步操作的结果。因此,一个Promise只是下面三种状态中的一种:
- pending:Promise的初始化状态
- fulfilled:成功操作的状态
- rejected:失败操作的状态
一旦一个promise处于fulfilled还是rejected,这个promise将不可改变。
构建Promise
一旦所有的API都返回了Promise,那么你不会经常需要手动构建promise。同时,我们需要一种方法去polyfil现有的API。比方说,我们会去重写readFile:
1 | function readFile(filename, enc){ |
我们用new Promise
创建promise实例。工厂函数创建一个promise,回调函数来做实际工作。这个函数伴随着两个参数,并被立即调用。第一个参数执行promise成功的工作,第二个参数拒绝promise。一旦实际工作完成后,我们会根据操作的状态调用响应的函数。
等待Promise
想要用Promise,我们必须在某种方式上可以支持等待他成功或失败。这种方式是利用promise.done
。利用这种思想,我们重新写上面的方法:1
2
3
4
5
6
7
8
9
10
11function readJSON(filename){
return new Promise(function (fulfill, reject){
readFile(filename, 'utf8').done(function (res){
try {
fulfill(JSON.parse(res));
} catch (ex) {
reject(ex);
}
}, reject);
});
}
这里仍然许多处的异常处理,但是写错误的机会少了。另外,我们也不再需要一些陌生又多余的参数。
非标准:用在例子里的promise.done
没有写入标准。但是他被很多promise的库支持。推荐对这个方法做polyfill。1
<script src="https://www.promisejs.org/polyfills/promise-done-7.0.4.min.js"></script>
转化/链式
按照我们都示例,我们真正想做的是通过另一个操作转化promise。在我们的案例中,第二个操作就是同步,即:JSON.parse是同步的。但在其他情况下,这个第二步操作也很可能是一个异步的。幸运的是,promise有API转化promise和链式的方法。
简单的说,.then和.done是等效的。换句话说,如果你要处理promise的结果的话,用.then,如果你只是想等待他结束而不处理值的话用.done。
现在,我们重写下这个案例:1
2
3
4
5function readJSON(filename){
return readFile(filename, 'utf8').then(function (res){
return JSON.parse(res);
});
}
因为JSON.parse也是一个函数,因此可以如下改写:1
2
3function readJSON(filename){
return readFile(filename, 'utf8').then(JSON.parse);
}
实现/Polyfills
p Promise在node.js和browser环境下都能使用。
jQuery
需要提醒的是,jQuery调用的Promise和其他的Promise不一样。jQuery没有一个好的API体系,这样会非常容易误解。幸运的是,你可以将jquery的Promise转化成标准的Promise:1
2
3var jQueryPromise = $.ajax('/data.json');
var realPromise = Promise.resolve(jQueryPromise);
//now just use `realPromise` however you like.
Browser
Promise目前只在一小部分浏览器上才能得到支持(http://kangax.github.io/compat-table/es6/)。但是这些不兼容都能用polyfills解决。(PS:目前来说大多数现代浏览器是支持的)1
<script src="https://www.promisejs.org/polyfills/promise-7.0.4.min.js"></script>
目前没有浏览器支持Promise.prototype.done
。所以如果你想用那个功能,你就需要用polyfill,至少需要引入下面的polyfill1
<script src="https://www.promisejs.org/polyfills/promise-done-7.0.4.min.js"></script>
Node.js
目前没有一个适当的实践在nodejs环境下做polyfill。但是你可以用require的方式引入。
安装promise1
npm install promise
这样就可以用require的方式去赋值给变量1
var Promise = require('promise');
promise库提供了很多比较实用的API,供开发者与node.js交互。1
2
3
4
5
6
7
8
9
10
11var readFile = Promise.denodeify(require('fs').readFile);
// 返回一个Promise
function readJSON(filename, callback){
// 如果callback存在,返回一个undefined。提供的callback
// 第一个参数是error,第二个参数是result。
// 如果callback不存在,返回一个promise
return readFile(filename, 'utf8')
.then(JSON.parse)
.nodeify(callback);
}
(下面是自己想的)
对比
现在返回来看下,比对下优化前后:1
2
3
4
5
6
7
8
9
10
11
12// before
function readJSON(filename, callback){
fs.readFile(filename, 'utf8', function (err, res){
if (err) return callback(err);
try {
res = JSON.parse(res);
} catch (ex) {
return callback(ex);
}
callback(null, res);
});
}
1 | // after |
前后对比,通过包裹一层Promise,最直观的感受是我们解耦了代码,将两部操作链式执行。同时减弱了代码侵入。另外,我们不用去关心callback的参数到底是什么,因为Promise用.then去解决了成功的回调。用.catch解决失败的回调。
因此,个人认为Promise的基本思想是将控制权限交给了开发者,实现了更加幽雅的编程体验。