JavaScript Promise
在 Promise 出现之前,如果大家需要实现异步操作,通用的做法是事件加上回调函数,如果我们有多个异步操作需要嵌套执行的话,那么代码将变得非常难于阅读。让我们来看一个具体的代码例子。在下面的例子中,我们首先通过 Http request 调用一个 web service,然后将从 web service 收到的数据写入一个本地文件。从任务的角度来看,这是一个非常简单的任务,但是当你第一次看到这个代码的时候,一定觉得头很晕,因为在这段代码中
- 对 web service 进行调用的代码和写文件的代码混杂在一起(写文件的代码嵌套在
end
事件的回调函数中),造成代码模块不清晰,阅读和理解起来比较费劲。 - 对错误的处理分散在代码的各个地方,而且错误处理的实现方式都不一样。对 web service 进行调用的代码,通过监听
error
事件来处理错误,并且将错误输出到控制套;而写文件的代码,是通过回调函数来处理错误,并将错误通过throw
语句抛出。
var http = require("http");
var fs = require("fs");
var querystring = require("querystring");
var postData = querystring.stringify({
'msg' : 'Hello World!'
});
var options = {
hostname: '127.0.0.1',
port: 8080,
path: '/upload',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length
}
};
var req = http.request(options, (res) => {
console.log(`STATUS: ${res.statusCode}`);
console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
res.setEncoding('utf8');
var dataReceived = ""
res.on('data', (chunk) => {
dataReceived = dataReceived + chunk.toString();
console.log(`BODY: ${chunk}`);
});
res.on('end', () => {
console.log('No more data in response.')
//now we try to write the message to a file
fs.writeFile("temp.txt", dataReceived, function(err){
if (err){
throw err;
}
console.log("Write file temp.txt succ");
});
})
});
req.on('error', (e) => {
console.log(`problem with request: ${e.message}`);
});
// write data to request body
req.write(postData);
req.end();
接下来,让我们使用 Promise 重写上面的代码。在重写的代码中,我们可以看到:
- 对 web service 进行调用的代码和写文件的代码完全分离开了,代码结构变得非常清晰。在阅读代码的过程中,不会再被不相关的代码所干扰。
- Promise 对象对外提供了统一的回调函数接口(resolve 和 reject 回调函数 ),在重写的代码中,我们可以很容易的把对web service的请求分装到一个模块中,从而对外隐藏Http Request的所有细节。
- 通过 Promise 对象的封装,对错误代码的处理被统一了,都是通过对
reject
函数调用来说明异步操作过程中有错误发生,而且错误被集中到.catch
代码段进行了处理(错误都被输出到了控制台)。 - 通过 Promise 的封装,异步操作的代码变得和同步操作代码很像,更方便其他人理解代码的处理逻辑。
'use strict';
var http = require("http");
var fs = require("fs");
var querystring = require("querystring");
var postData = querystring.stringify({
'msg' : 'Hello World!'
});
var options = {
hostname: '127.0.0.1',
port: 8080,
path: '/upload',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length
}
};
var pHttpRequest = new Promise(function(resolve, reject){
let req = http.request(options, (res) => {
console.log(`STATUS: ${res.statusCode}`);
console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
res.setEncoding('utf8');
let dataReceived = ""
res.on('data', (chunk) => {
dataReceived = dataReceived + chunk.toString();
});
res.on('end', () => {
resolve(dataReceived);
})
});
req.on('error', function(e){
reject(e);
});
req.write(postData);
req.end();
})
pHttpRequest.then(
//http request promise成功时候的处理
//在http request promise成功的时候,开始处理写文件操作
function(dataReceived){
return new Promise(function(resolve, reject){
fs.writeFile("temp.txt", dataReceived, function(e){
if (e) reject(e);
else resolve("Write file succ");
});
})
}
).then(
//writeFile promise成功时候的处理
function(msg){
console.log(msg);
}
).catch(
//全局错误处理
function(err){
console.log("some error happen(" + err + ")");
}
)
正式基于 Promise 对象有上面所说的这些优点,ES6 正式将 Promise 对象编程了系统的一个内置对象,大家再也不用通过第三方库开始用 Promise 对象了。
Promise 对象的特性
- Promise 对象一旦创建,就开始执行了,你没有办法取消 Promise 对象的执行。
- Promise 对象的状态只由异步操作的结果决定,没有任何其他的操作可以改变Promise对象的状态如果异步操作执行成功,Promise 对象将进入 Resolved 状态,同时resolve函数将被调用;如果异步操作执行失败,Promise 对象将进入 Rejected 状态,同时 reject 函数将被调用。
- Promise对象一旦进入 Resolved 或者 Rejected 状态,状态将不可能再发生变化,在 Promise 对象被销毁之前,将一直保持 Resolved 或者 Rejected 状态。
- 因为Promise对象的实现方法,在异步操作过程中出现的异常是不会被抛出的,因此需要在 Promise 对象内部进行处理(通过提供
.catch
代码段来实现)。
下图表示了一个 Promise 对象的整个生命周期。
Promise 对象的使用
创建 Promise 对象
在 ES6 中,你可以通过 new Promise(function(resolve, reject){
来创建 Promise 对象,下面是一个具体的代码。
})
'use strict';
var fs = require("fs");
var promiseObj = new Promise(function(resolve, reject){
//put some code to call
fs.readFile("temp.txt", (err, data) => {
if (err){
reject(err);
}else{
resolved(data);
}
});
});
关联 resolve function 和 reject function
在上面的例子中,如果你执行这个代码,你会发现没有任何的效果(没有输出),那是因为你没有给这个创建的 Promise 对象关联相应的 resolve 和 reject 函数。下面是一个进一步的例子,这个例子将给 Promise 对象绑定 resolve 和 reject 函数,你就可以看到效果了。在我的测试环境中,因为我们有名为 temp.txt 的文件存在,所以输出了 file read fail
。
'use strict';
var fs = require("fs");
var promiseObj = new Promise(function(resolve, reject){
//put some code to call
fs.readFile("temp.txt", (err, data) => {
if (err){
reject(err);
}else{
resolved(data);
}
});
});
promiseObj.then(function(data){
console.log("file read succ");
}, function(err){
console.log("file read fail");
})
在通常情况下,reject 状态的函数我们一般不在 then 中设置,而是在 catch 中设置,这样代码看起来更像是传统意义上的同步代码(和try...catch 比较)。因此上面的例子可以重新写成
'use strict';
var fs = require("fs");
var promiseObj = new Promise(function(resolve, reject){
//put some code to call
fs.readFile("temp.txt", (err, data) => {
if (err){
reject(err);
}else{
resolved(data);
}
});
});
promiseObj.then(function(data){
console.log("file read succ");
}).catch(function(err){
console.log("file read fail");
});
级联多个 Promise 对象
Promise 对象的 resolve 函数的参数可以是另外一个 Promise 对象,这样就可以将 2 个 Promise 对象级联起来。下面是一个简单的例子
'use strict';
var p1 = new Promise(function(resolve, reject){
setTimeout(() => reject(new Error("something test"), 3000));
});
var p2 = new Promise(function(resolve, reject){
setTimeout(() => resolve(p1), 1000);
});
p2.then(function(data){
console.log("p2 succ");
}).catch(function(err){
console.log("p2 fail");
});
Promise 的特殊函数
Promise.all()
将多个 Promise 包装成一个全新的 Promise Object,如果所有的 Promise 被 Resolved,那么新的 Promise 将被 Resolve;否则新的 Promise 将被 Reject。
Promise.race()
和 .all 一样,.race 将把多个 Promise 包装成一个新的 Promise Object,不同的地方是。这些 Promise 之中任何一个 Resolve 或者 Reject 了,新的 Promise 就被 Resolve 或者 Reject 了。
Promise.resolve()
将传入的对象封装成一个 Promise 对象返回。resolve 方法根据以下的规则返回 Promise 对象。
- 如果输入的参数本身是一个 Promise 对象,那么 resolve 方法直接返回这个对象;
- 如果输入的对象本身有 then 方法(必须是一个可以接受2个 function 的方法),那么 resolve 将这个对象转换成 Promise 对象,并理解调用 then 方法;下面是一个例子
//for this example, you will see following output
// then function in thenobject
// Promise object resolve function is called Then function is called
var thenobject = {
then: function(resolve, reject){
console.log("then function in thenobject");
resolve("Then function is called");
}
};
var pObj = Promise.resolve(thenobject);
pObj.then(function(data){
console.log("Promise object resolve function is called " + data);
});
- 如果传入的参数就是一个普通对象,那么返回的 Promise 对象直接处于 resolved 状态,并且输入的参数将作为 resolved 状态下调用的函数的参数。下面是一个具体的例子。
//for this example, you will see following output
// Promise object resolve function is called Hello World!
var pObj = Promise.resolve("Hello World!");
pObj.then(function(data){
console.log("Promise object resolve function is called " + data);
});
如果没有输入参数,那么返回的Promise对象直接处于 resolved 状态,并且 resolved 状态下调用的函数没有输入参数。
done() method
通过在Promise的调用链最后使用这个方法,可以保证catch到任何的错误。
finally() method
如果你需要在 Promise 结束的时候(不管 resolve 还是 reject 结束),都有一个函数被调用,那么就需要使用这个方法。这个方法接受一个回调函数作为输入。当 Promise 结束的时候,这个回调函数将被调用。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: ES6 模板字符串
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论