一、简介
Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口。
以上定义引自阮一峰《Javascript异步编程的4种方法》。
我的简单理解是,Promise是JavaScript异步回调的解决方案之一,最大的优点是以链式调用的方式来消除Callback Hell的问题。
二、用法
1、运用一个Promise对象可以总结成两个步骤:
- 异步。决定回调的时机(when)。
- 回调。实现回调的内容(what)。
举个栗子,通常网络模块的业务逻辑是:请求数据、解析返回的数据并显示。为了代码写起来简单一点,这里抽象成“进行耗时操作(比如timeout个3秒)、显示数据”。
在这里,“异步”就是耗时操作的过程:
// 定义一个Promise对象,通过new Promise的方式
// 参数是待定的。resolveCallback是成功的回调,rejectCallback是失败的回调
// resolveCallback和rejectCallback,只有其中一个会被执行
var promise = new Promise(function (resolveCallback, rejectCallback) {
setTimeout(function () {
resolveCallback("this is the returned data.");
}, 3000);
});
“回调”就是之后的把数据显示在View上的过程:
// 用then,参数是两个回调函数的实现:一个成功的回调,一个失败的回调
promise
.then(function (data) {
console.log(data); // 成功的具体的回调,把data显示在界面上
}, function (error) {
console.log(error); // 失败的具体回调
})
2、优点:链式调用
因为每次使用then,会返回Promise对象本身,而这个Promise对象又有then方法,所以可以一直调用下去,称之为“链式调用”。这也是Promise最大的优点,我们通常说它“更优雅的异步回调”,是因为它解决了Callback Hell的问题。链式调用最关键的地方在于,后一个then的回调参数,是前一个then的回调中的返回值,就像这样:
// 异步过程
var promise = new Promise(function (resolveCallback, rejectCallback) {
setTimeout(function () {
resolveCallback("this is the returned data.");
}, 2000);
});
// 回调过程
promise
.then(function (data) {
console.log(data);
return data + "again."; //return是给下一个then的参数。缺少就是undefined
}, function (error) {
console.log(error);
})
.then(function (data) { // 这里接收到上一个then给的参数
console.log(data);
}, function (error) {
console.log(error);
});
// 可以无限then下去……
结果截图:
三、对比
1、与Android中接口回调的联系:
上面说到的两个步骤,“异步”和“回调”,很容易让人联想到Android中类似的做法。在Android SDK代码中,如果想要进行网球请求的异步回调,通常的做法是定义一个interface来作为回调的媒介,在这个接口中,至少含有请求成功、失败的回调。
// 定义网络请求回调的统一接口
public interface NetworkCallback {
void onSuccess(String response); //成功的回调,待实现
void onError(String error); //失败的回调,待实现
}
注意,以下代码是为了简便而简写,可能有错误的地方,比如一些try catch就暂时省略。
// 异步请求。决定回调的时机。
// 假定该文件是NetworkHelper.class
public void get(String url, NetworkCallback cb) {
HttpUrlConnection conn = (HttpUrlConnection) new URL(url).openConnection();
if (conn.getStatusCode() == 200) { // 如果请求OK
String data;
//...get data from InputStream
cb.onSuccess(data); // 回调成功的函数
} else {
cb.onError("Error..."); // 回调失败的函数
}
}
// 回调。实现回调的内容,也就是实现上面定义的接口。
NetworkHelper.get("http://www.google.com", new NetworkCallback() {
@Override
public void onSuccess(String response) {
// 处理、显示返回的结果
}
@Override
public void onError(String error) {
// 处理错误
}
});
可见,Android中的异步回调的这种做法,跟Promise的使用过程是很相似的,因为思想是一样的,就是异步+回调。不过,Promise自身多了一个链式调用的优点。
2、与NodeJS中经典的异步API的比较:
NodeJS本身提供了许多异步的“错误优先”的API,比如一个读取文件的函数readFile,假如我想要读取文件完毕之后,打印出一句话“Done!”,那一般的做法是:
var fs = require('fs');
function read(uri) {
fs.readFile(uri, function (err, data) {
if (!err) {
console.log('Done!');
// 这里可能还会有更多异步操作,就会造成Callback Hell
} else {
}
});
}
为了避免Callback Hell,我们可以把它封装成一个Promise对象:
var fs = require('fs');
function read(uri) {
return new Promise(function (resolve, reject) {
fs.readFile(uri, function (err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
read('./../images/test.png')
.then(function (data) {
console.log('length of file1:', data.toString().length);
return read('./hello.js'); // 这里返回一个promise对象以链式调用
}, function (err) {
console.log(err);
})
.then(function (data) {
console.log('length of file2:', data.toString().length)
}, function (err) {
console.log(err);
});
效果:
如果想要深入了解链式调用的封装效果,推荐这篇博客:http://swiftcafe.io/2016/07/27/updator/
3、与RxJava(RxAndroid)的类比:
把嵌套回调写成链式,除了Promise,还有RxJava。不过在这里就不详细展开了,就大致列一下它们之间相同和不同的地方吧。
相同点:
- 链式解决Callback Hell。
不同点:
- RxJava是面向数据流的,将数据封装成特有的类型“Observable”;并且,提供对Observable的一系列便捷的操作,即函数式编程。而Promise没有对数据的更进一步的封装。
- RxJava可以方便地在线程之间切换,实现更高权限的控制。