很久没看es6了,昨天遇到一个与promise相关的bug,记录下promise的相关知识了javascript
阮一峰老师的Promisejava
Promise对象是一个构造函数,用来生成Promise实例。es6
const promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操做成功 */){ resolve(value); } else { reject(error); } });
####异步加载图片json
function loadImageAsync(url) { return new Promise(function(resolve, reject) { const image = new Image(); image.onload = function() { resolve(image); }; image.onerror = function() { reject(new Error('Could not load image at ' + url)); }; image.src = url; }); }
####Promise对象实现的 Ajax 操做 getJSON是对 XMLHttpRequest 对象的封装,用于发出一个针对 JSON 数据的 HTTP 请求,而且返回一个Promise对象。须要注意的是,在getJSON内部,resolve函数和reject函数调用时,都带有参数。数组
const getJSON = function(url) { const promise = new Promise(function(resolve, reject){ const handler = function() { if (this.readyState !== 4) { return; } if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; const client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = handler; client.responseType = "json"; client.setRequestHeader("Accept", "application/json"); client.send(); }); return promise; }; getJSON("/posts.json").then(function(json) { console.log('Contents: ' + json); }, function(error) { console.error('出错了', error); });
####注意小点promise
通常来讲,调用resolve或reject之后,Promise 的使命就完成了,后继操做应该放到then方法里面,而不该该直接写在resolve或reject的后面。因此,最好在它们前面加上return语句,这样就不会有意外。app
new Promise((resolve, reject) => { return resolve(1); // 后面的语句不会执行 console.log(2); })
Promise 实例具备then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的做用是为异步
Promise 实例添加状态改变时的回调函数。函数
then方法的第一个参数 resolved状态的回调函数, 第二个参数(可选) rejected状态的回调函数。oop
能够采用链式写法 链式中后一个then的执行是要在前一个then返回的Promise对象变化时再执行。。
getJSON("/post/1.json").then( post => getJSON(post.commentURL) ).then( comments => console.log("resolved: ", comments), err => console.log("rejected: ", err) );
Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
// 基本用法 getJSON('/posts.json').then(function(posts) { // ... }).catch(function(error) { // 处理 getJSON 和 前一个回调函数运行时发生的错误 console.log('发生错误!', error); }); p.then((val) => console.log('fulfilled:', val)) .catch((err) => console.log('rejected', err)); // 等同于, p.then((val) => console.log('fulfilled:', val)) .then(null, (err) => console.log("rejected:", err));
第二个then第一个参数null,只有rejected处理,
promise抛出一个错误,就被catch方法指定的回调函数捕获。
// 写法一 const promise = new Promise(function(resolve, reject) { try { throw new Error('test'); } catch(e) { reject(e); } }); promise.catch(function(error) { console.log(error); }); // 写法二 const promise = new Promise(function(resolve, reject) { reject(new Error('test')); }); promise.catch(function(error) { console.log(error); });
reject方法的做用,等同于抛出错误。
const 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) });
Promise 在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。由于
** Promise 的状态一旦改变,就永久保持该状态**,
// bad promise .then(function(data) { // success }, function(err) { // error }); // good promise .then(function(data) { //cb // success }) .catch(function(err) { // error });
不会再变了。
Promise 对象的错误具备**“冒泡”性质**,会一直向后传递,直到被捕获为止。也就是说,错误老是会被下一个catch语句捕获。 可是这种冒泡只有链条中能够处理
跟传统的try/catch代码块不一样的是,若是没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应
Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。
finally方法用于指定无论 Promise 对象最后状态如何,都会执行的操做。该方法是 ES2018 引入标准的。
promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});
无论promise最后的状态,在执行完then或catch指定的回调函数之后,都会执行finally方法指定的回调函数。
finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态究竟是fulfilled仍是rejected。这代表,
finally方法里面的操做,应该是与状态无关的,
不依赖于 Promise 的执行结果。
finally本质上是then方法的特例。
promise .finally(() => { // 语句 }); // 等同于 promise .then( result => { // 语句 return result; }, error => { // 语句 throw error; } );
Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
p的状态由p一、p二、p3决定,分红两种状况。
(1)只有p一、p二、p3的状态都变成fulfilled,
p的状态才会变成fulfilled,此时p一、p二、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p一、p二、p3之中有一个被rejected,
p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
// 生成一个Promise对象的数组 const promises = [2, 3, 5, 7, 11, 13].map(function (id) { return getJSON('/post/' + id + ".json"); }); Promise.all(promises).then(function (posts) { // ... }).catch(function(reason){ // ... });
遍历接口的好方法。。
若是做为参数的 Promise 实例,本身定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。
const p1 = new Promise((resolve, reject) => { resolve('hello'); }) .then(result => result) .catch(e => e); const p2 = new Promise((resolve, reject) => { throw new Error('报错了'); }) .then(result => result) .catch(e => e); Promise.all([p1, p2]) .then(result => console.log(result)) .catch(e => console.log(e)); // ["hello", Error: 报错了]
p1会resolved,p2首先会rejected,可是p2有本身的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的其实是这个实例。该实例执行完catch方法后,也会变成resolved,致使Promise.all()方法参数里面的两个实例都会resolved,所以会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。
若是p2没有本身的catch方法,就会调用Promise.all()的catch方法。
一样是将多个 Promise 实例,包装成一个新的 Promise 实例。
只要p一、p二、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
Promise.race方法的参数与Promise.all方法同样,若是不是 Promise 实例,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
将现有对象转为 Promise 对象,Promise.resolve方法就起到这个做用。
Promise.resolve('foo') // 等价于 new Promise(resolve => resolve('foo'))
Promise.resolve方法的参数分红四种状况。
(1)参数是一个 Promise 实例
若是参数是 Promise 实例,那么Promise.resolve将不作任何修改、原封不动地返回这个实例。
(2)参数是一个thenable对象
thenable对象指的是具备then方法的对象,好比下面这个对象。
let thenable = { then: function(resolve, reject) { resolve(42); } };
Promise.resolve方法会将这个对象转为 Promise 对象,而后就当即执行thenable对象的then方法。
let thenable = { then: function(resolve, reject) { resolve(42); } }; let p1 = Promise.resolve(thenable); p1.then(function(value) { console.log(value); // 42 });
上面代码中,thenable对象的then方法执行后,对象p1的状态就变为resolved,从而当即执行最后那个then方法指定的回调函数,输出 42。
(3)参数不是具备then方法的对象,或根本就不是对象
若是参数是一个原始值,或者是一个不具备then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
const p = Promise.resolve('Hello'); p.then(function (s){ console.log(s) }); // Hello
上面代码生成一个新的 Promise 对象的实例p。因为字符串Hello不属于异步操做(判断方法是字符串对象不具备 then 方法),返回 Promise 实例的状态从一辈子成就是resolved,因此回调函数会当即执行。Promise.resolve方法的参数,会同时传给回调函数。
(4)不带有任何参数
Promise.resolve方法容许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
因此,若是但愿获得一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve方法。
const p = Promise.resolve(); p.then(function () { // ... });
上面代码的变量p就是一个 Promise 对象。
须要注意的是,当即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
setTimeout(function () { console.log('three'); }, 0); Promise.resolve().then(function () { console.log('two'); }); console.log('one'); // one // two // three
上面代码中, setTimeout(fn, 0)在下一轮“事件循环”开始时执行,
Promise.resolve()在本轮“事件循环”结束时执行,
console.log('one')则是当即执行,所以最早输出。