Promise是抽象异步处理对象以及对其进行各类操做的组件。(Promise并非从JavaScript中发源的概念)。简单说就是一个容器, 里面保存着某个将来才会结束的事件( 一般是一个异步操做)的结果。javascript
JavaScript中是经过回调函数来处理异步逻辑的,好比读取文件的代码,以下所示html
getAsync("fileA.txt", function(error, result){
if(error){
// 取得失败时的处理 throw error;
}
}
复制代码
Nodejs中规定在Javascript的回掉函数的第一个参数是Error
对象。像上面这样基于回调函数的异步处理若是统一参数使用规则的话,写法也会很明了。可是,这也仅是编码规约而已,即便采用不一样的写法也不会出错。java
Promise则是把相似的异步处理对象和处理规则进行规范化,并按照采用统一的接口来编写,而采起规定方法以外的写法都会出错。node
下面经过Promise写法改写上面的函数es6
var promise = getAsyncPromise("fileA.txt"); //返回promise对象
promise.then(function(result){
// 获取文件内容成功时的处理
}).catch(function(error){
// 获取文件内容失败时的处理
});
复制代码
咱们能够向这个预设了抽象化异步处理的promise对象,注册这个promise对象执行成功 时和失败时相应的回调函数。编程
这和回调函数方式相比有哪些不一样之处呢? 在使用promise进行一步处理的时候,咱们 必须按照接口规定的方法编写处理代码。json
也就是说,除promise对象规定的方法(这里的 then
或 catch
)之外的方法都是不可使用的,而不会像回调函数方式那样能够本身自由的定义回调函数的参数,而必须严格遵照固定、统一的编程方式来编写代码。api
这样,基于Promise的统一接口的作法, 就能够造成基于接口的各类各样的异步处理模 式。因此,promise的功能是能够将复杂的异步处理轻松地进行模式化,这也能够说得上是 使用promise的理由之一。promise
要想建立一个Promise对象,可使用new
调用Promise
的构造器来进行实例化。浏览器
var promise = new Promise(function(resolve, reject) { // 异步处理
// 处理结束后、调用resolve 或 reject
});
复制代码
对经过new生成的promise对象为了设置其值在 resolve (成功)/ reject(失败)时调用的回调函数 可使用 promise.then()
实例方法(也就是说做用是为 Promise 实例添加状态改变时的回调函数。)。
promise.then(onFulfilled, onRejected)
复制代码
then
方法的第一个参数是 Resolved 状态的回调函数, 第二个参数( 可选) 是 Rejected 状态的回调函数。
onFulfilled 、 onRejected 两个都为可选参数。
then
方法返回的是一个新的 Promise 实例( 注意,不是原来那个 Promise 实例)。 所以能够采用链式写法, 即then
方法后面再调用另外一个`then方法。
getJSON("/post/1.json") //返回一个Promise对象,详见下文
.then(function(post) {
return getJSON(post.commentURL); //返回一个Promise对象
})
.then(function funcA(comments) {
console.log("Resolved: ", comments);
}, function funcB(err) {
console.log("Rejected: ", err);
});
复制代码
上面的代码使用then
方法,依次指定了两个回调函数。 第一个回调函数完成之后,会将返回结果做为参数,传入第二个回调函数。采用链式的then,能够指定一组按照次序调用的回调函数。
promise.then
成功和失败时均可以使用。另外在只想对异常进行处理时能够采用Promise.then(undefined, onRejected)
这种方式,只指定reject时的回调函数便可。Promise.prototype.catch
方法是.then(null, rejection)
的别名, 用于指定发生错误时的回调函数,等同于抛出错误。 上文的代码能够改形成以下
getJSON("/post/1.json") //返回一个Promise对象,详见下文
.then(function(post) {
return getJSON(post.commentURL); //返回一个Promise对象
})
.then(function (comments) {
console.log("Resolved: ", comments);
})
.catch(err) {
console.log("Rejected: ", err);
});
复制代码
须要注意的是,若是 Promise 状态已经变成Resolved, 再抛出错误是无效的。
var promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise
.then(function(value) {
console.log(value)
})
.catch(function(error) {
console.log(error)
});
// ok
复制代码
上面代码中, Promise 在resolve语句后面,再抛出错误,不会被捕获, 等于没有抛出。
Promise 对象的错误具备“ 冒泡” 性质, 会一直向后传递, 直到被捕获为止。 也就是说, 错误老是会被下一个catch语句捕获。
var catchTest = new Promise(function(resolve, reject) {
setTimeout(function(){
resolve('aa')
}, 1000)
})
catchTest
.then(function(value){
console.log('a')
})
.then(function(value){
throw new Error('test');
console.log('b')
})
.then(function(value){
console.log('c')
})
.catch(function(error){
console.log(error)
})
//a
//[Error: test]
复制代码
上面代码中,一共有四个Promise 对象:一个由'catchTest'产生, 三个由then产生。它们之中的第二个then方法出了错误,中断了下面的then方法,直接被最后一个catch捕获。
建议老是使用catch方法, 而不使用then方法的第二个处理错误的参数。
跟传统的try / catch
代码块不一样的是,若是没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码, 即不会有任何反应。
var someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,由于 x 没有声明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
复制代码
上面代码中,someAsyncThing
函数产生的 Promise 对象会报错, 可是因为没有指定catch方法,这个错误不会被捕获,也不会传递到外层代码, 致使运行后没有任何输出。
注意, Chrome 浏览器不遵照这条规定, 它会抛出错误“ ReferenceError: x is not defined”。
var promise = new Promise(function(resolve, reject) {
resolve("ok");
setTimeout(function() {
throw new Error('test')
}, 0)
});
promise.then(function(value) {
console.log(value)
});
// ok
// Uncaught Error: test
复制代码
上面代码中,Promise指定在下一轮“ 事件循环” 再抛出错误, 结果因为没有指定使用try...catch语句
,就冒泡到最外层,成了未捕获的错误。 由于此时,Promise 的函数体已经运行结束了, 因此这个错误是在Promise函数体外抛出的。
Node.js 有一个unhandledRejection
事件,专门监听未捕获的reject错误。unhandledRejection
事件的监听函数有两个参数, 第一个是错误对象, 第二个是报错的 Promise 实例, 它能够用来了解发生错误的环境信息。
process.on('unhandledRejection', function(err, p) {
console.error(err.stack)
});
复制代码
须要注意的是,catch方法返回的仍是一个Promise对象,所以后面还能够接着调用then方法。
var someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,由于 x 没有声明
resolve(x + 2);
});
};
someAsyncThing()
.catch(function(error) {
console.log('oh no', error);
})
.then(function() {
console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// carry on
复制代码
上面代码运行完catch方法指定的回调函数,会接着运行后面那个then方法指定的回调函数。 若是没有报错, 则会跳过catch方法。
像 Promise 这样的全局对象还拥有一些静态方法。
包括 Promise.all()
还有 Promise.resolve()
等在内,主要都是一些对Promise进行操做的 辅助方法。
咱们已经大概了解了Promise的处理流程,接下来让咱们来稍微整理一下Promise的状态。
用 new Promise 实例化的promise对象有如下三个状态。
关于上面这三种状态的读法,其中左侧为在 ES6 Promises 规范中定义的术语, 而右侧则是在 Promises/A+ 中描述状态的术语。
promise对象的状态,从Pending转换为Fulfilled或Rejected以后, 这个promise对象的状态就不会再发生任何变化。也就是说,只有异步操做的结果能够决定当前是哪种状态,其余任何操做都没法改变这种状态;一旦状态改变,就不会再改变。
Promise与Event等不一样,在
.then
后执行的函数能够确定地说只会被调用一次。
还有须要注意,Promise建立后回马上执行,看下面代码
var promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('Resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// Resolved
复制代码
上面代码中, Promise 新建后当即执行, 因此首先输出的是“ Promise”。 而后, then方法指定的回调函数, 将在当前脚本全部同步任务执行完才会执行, 因此“ Resolved” 最后输出。
Promise也是有缺点的
下面介绍一下如何编写一下Promise代码。
new Promise(fn)
返回一个Promise对象fn
中指定异步等处理逻辑 • 处理结果正常的话,调用 resolve(处理结果值) • 处理结果错误的话,调用 reject(Error对象)按照这个流程,咱们来写一段promise代码吧。任务是用Promise经过异步处理方式来获取XMLHttpRequest(XHR)的数据。
首先,建立一个用Promise把XHR处理包装起来的名为 getURL
的函数。
function getURL(URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
// 运行示例
var URL = "http://httpbin.org/get";
getURL(URL).then(function onFulfilled(value){
console.log(value);
}).catch(function onRejected(error){
console.error(error);
});
复制代码
getURL
只有在经过XHR取得结果状态为200
时才会调用resolve
,而其余状况(取得失败)时则会调用reject
方法。
resolve(req.responseText) resolve
函数的做用是, 将 Promise
对象的状态从“ 未完成” 变为“ 成功”( 即从 Pending
变为Resolved
),在异步操做成功时调用,并将异步操做结果,做为参数传递出去。 参数并无特别的规则,基本上把要传给回调函数参数放进去就能够了。 ( then 方法能够接收到这个参数值)
reject(new Error(req.statusText)); reject
函数的做用是,将 Promise
对象的状态从“ 未完成” 变为“ 失败”( 即从 Pending
变为Rejected
),在异步操做失败时调用,并将异步操做报出的错误,做为参数传递出去。
上文中,XHR中 onerror
事件被触发的时候就是发生错误时,因此理所固然调用 reject
。发生错误时,建立一个Error
对象后再将具体的值传进去。传给 的参数也没有什么特殊的限制,通常只要是Error
对象(或者 继承自Error对象
)就能够。
var getJSON = function(url) {
var promise = new Promise(function(resolve, reject) {
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
function handler() {
if(this.readyState !== 4) {
return;
}
if(this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error(' 出错了 ', error);
});
复制代码
上面代码中,getJSON
是对 XMLHttpRequest
对象的封装, 用于发出一个针对 JSON
数据的 HTTP
请求, 而且返回一个 Promise 对象。 须要注意的是,在getJSON
内部, resolve
函数和reject
函数调用时, 都带有参数。关于参数传递,上文作过简要介绍
若是调用resolve
函数和reject
函数时带有参数,那么它们的参数会被传递给回调函数。 reject函数的参数一般是 Error 对象的实例,表示抛出的错误; resolve函数的参数除了正常的值之外,还多是另外一个 Promise
实例, 表示异步操做的结果有多是一个值,也有多是另外一个异步操做,好比像下面这样。
var p1 = new Promise(function(resolve, reject) {
// ...
});
var p2 = new Promise(function(resolve, reject) {
// ...
resolve(p1);
})
复制代码
上面代码中,p1
和p2
都是 Promise
的实例, 可是p2
的resolve
方法将p1
做为参数,即一个异步操做的结果是返回另外一个异步操做。
注意,这时p1
的状态就会传递给p2
,也就是说,p1
的状态决定了p2
的状态。若是p1
的状态是Pending
,那么p2
的回调函数就会等待p1
的状态改变; 若是p1
的状态已是Resolved
或者Rejected
, 那么p2
的回调函数将会马上执行。
var p1 = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
var p2 = new Promise(function(resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2.then(result => console.log(result))
.catch(error => console.log(error))
复制代码
上面代码中,p1
是一个 Promise
, 3 秒以后变为rejected
。 p2
的状态在 1 秒以后改变, resolve
方法返回的是p1
。 此时, 因为p2
返回的是另外一个Promise, 因此后面的then
语句都变成针对后者( p1)。 又过了 2 秒 p1
变为rejected
,致使触发catch
方法指定的回调函数。
关于Promise 你真的了解多少? es6中的Promise JavaScript Promise 迷你书 中文版 JavaScript Promise 迷你书