Promise是异步编程的一种解决方案,它有三种状态,分别是pending-进行中、resolved-已完成、rejected-已失败。比传统的解决方案(回调函数和事件)更合理更强大。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件 (通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。
Promise对象有以下2个特点:
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。
只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。
如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。
这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了Promise对象,就可以把异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供了统一的接口(resolved统一只指fulfilled状态,不包含rejected状态。),使得控制异步操作更加容易。
Promise也有一些缺点。 首先, 无法取消Promise, 一旦新建它就会立即执行, 无法中途取消。 其次, 如果不设置回调函数, Promise内部抛出的错误,不会反应到外部。 第三, 当处于Pending状态时, 无法得知目前进展到哪一个阶段( 刚刚开始还是即将完成)。
如果某些事件不断地反复发生, 一般来说, 使用 stream 模式是比部署Promise更好的选择。
ES6规定,Promise对象是一个构造函数,用来生成Promise实例
// 方法1
let promise = new Promise ( (resolve, reject) => {
if ( success ) {
resolve(a) // pending ——> resolved 参数将传递给对应的回调方法
} else {
reject(err) // pending ——> rejectd
}
})
// 方法2
function promise () {
return new Promise ( function (resolve, reject) {
if ( success ) {
resolve(a)
} else {
reject(err)
}
}
)}
注意:实例化的Promise对象会立即执行
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,又JavaScript引擎提供,不是自己部署。
resolve函数的作用,将Promise对象的状态从“未完成”变成“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
这个Promises对象有一个then方法,允许指定回调函数,在异步任务完成后调用。
可以用then方法分别制定Resolved状态和Rejected状态的回调函数:
promise.then(function(value){
// sucess
},function(error){
// failure
});
then方法可以接受2个回调函数作为参数,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
下面是一个Promise对象的简单例子:
let promise = new Promise(function(resolve,rejeact){
console.log('Promise');
resolve(data);//
});
promise.then(function(data){
console.log('Resolved');//后面可以用传过来的数据data做些其他操作
console.log(data);
});
// Promise
// Resolved
上面代码中,Promise新建后立即执行,所以首先输出的是”Promise”,
然后then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以”Resolved”最后输出。
Promise 实例具有then方法, 也就是说, then方法是定义在原型对象 Promise.prototype 上的。 它的作用是为 Promise 实例添加状态改变时的回调函数。
前面说过, then方法的第一个参数是 Resolved 状态的回调函数, 第二个参数( 可选) 是 Rejected 状态的回调函数。
then方法返回的是一个新的 Promise 实例( 注意, 不是原来那个 Promise 实例)。 因此可以采用链式写法, 即then方法后面再调用另一个then方法。
采用链式的then, 可以指定一组按照次序调用的回调函数。 这时, 前一个回调函数, 有可能返回的还是一个 Promise 对象( 即有异步操作), 这时后一个回调函数, 就会等待该 Promise 对象的状态发生变化, 才会被调用。
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function funcA(comments) {
console.log("Resolved: ", comments);
}, function funcB(err) {
console.log("Rejected: ", err);
});
如果采用箭头函数, 上面的代码可以写得更简洁。
getJSON("/post/1.json").then(
post => getJSON(post.commentURL)
).then(
comments => console.log("Resolved: ", comments),
err => console.log("Rejected: ", err)
);
上面代码中, 第一个then方法指定的回调函数, 返回的是另一个 Promise 对象。 这时, 第二个then方法指定的回调函数, 就会等待这个新的 Promise 对
象状态发生变化。 如果变为 Resolved, 就调用funcA, 如果状态变为 Rejected, 就调用funcB。
Promise.prototype.catch方法是.then(null, rejection) 的别名, 用于指定发生错误时的回调函数。
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
上面代码中, getJSON方法返回一个 Promise 对象, 如果该对象状态变为Resolved, 则会调用then方法指定的回调函数; 如果异步操作抛出错误, 状态就会变为Rejected, 就会调用catch方法指定的回调函数, 处理这个错误。 另外, then方法指定的回调函数, 如果运行中抛出错误, 也会被catch方法捕获。
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
catch()的作用是捕获Promise的错误,与then()的rejected回调作用几乎一致。但是由于Promise 对象的错误具有“ 冒泡” 性质, 会一直向后传递, 直到被捕获为止。 也就是说, 错误总是会被下一个catch语句捕获。catch()总能够捕获then()中抛出的错误,所以建议不要使用then()的rejected回调,而是统一使用catch()来处理错误(具体解释请参考 沅一峰 ECMAScript 6 入门)
var someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为 x 没有声明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
return someOtherAsyncThing();
}).catch(function(error) {
console.log('oh no', error);
// 下面一行会报错,因为 y 没有声明
y + 2;
}).catch(function(error) {
console.log('carry on', error);
});
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]
上面代码中, 第二个catch方法用来捕获, 前一个catch方法抛出的错误。
同样,catch()中也可以抛出错误,由于抛出的错误会在下一个catch中被捕获处理,因此可以再添加catch()
需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用。
var jsPromise = Promise.resolve($.ajax('/whatever.json'));
上面代码将 jQuery 生成的deferred对象, 转为一个新的 Promise 对象。
Promise.resolve等价于下面的写法。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
Promise.resolve方法的参数分成四种情况
(1) 参数是一个 Promise 实例: 原样返回
如果参数是 Promise 实例, 那么Promise.resolve将不做任何修改、 原封不动地返回这个实例。
(2) 参数是一个thenable 对象 : 转换为Promise后立即执行then方法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。
如果参数是一个原始值, 或者是一个不具有then方法的对象, 则Promise.resolve方法返回一个新的 Promise 对象, 状态为Resolved。
var p = Promise.resolve('Hello');
p.then(function(s) {
console.log(s)
});
// Hello
上面代码生成一个新的 Promise 对象的实例p。 由于字符串Hello不属于异步操作( 判断方法是它不是具有 then 方法的对象), 返回 Promise 实例的状态从一生成就是Resolved, 所以回调函数会立即执行。 Promise.resolve方法的参数, 会同时传给回调函数。
所以, 如果希望得到一个 Promise 对象, 比较方便的方法就是直接调用Promise.resolve方法。
var 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‘) 则是立即执行, 因此最先输出。
Promise.all方法用于将多个 Promise 实例, 包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
var promise = Promise.all( [p1, p2, p3] )
promise.then(
...
).catch(
...
)
如下面列子:
// 生成一个 Promise 对象的数组
var 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.all方法接受一个数组作为参数, p1、 p2、 p3都是 Promise 对象的实例, 如果不是, 就会先调用下面讲到的Promise.resolve方法, 将参数转为 Promise 实例, 再进一步处理。( Promise.all方法的参数可以不是数组, 但必须具有 Iterator 接口, 且返回的每个成员都是 Promise 实例。)
p的状态由p1、 p2、 p3决定, 分成两种情况。
( 1) 只有p1、 p2、 p3的状态都变成fulfilled, p的状态才会变成fulfilled, 此时p1、 p2、 p3的返回值组成一个数组,会传递给p(调用then()的已完成回调)的回调函数
( 2) 只要p1、 p2、 p3之中有一个被rejected, p的状态就变成rejected, 此时第一个被reject的实例的返回值, 会传递给p的回调函数。
Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
var promise = Promise.race( [p1, p2, p3] )
promise.then(
...
).catch(
...
)
上面代码中, 只要p1、 p2、 p3之中有一个实例率先改变状态, p的状态就跟着改变。 那个率先改变的 Promise 实例的返回值, 就传递给p的回调函数。
下面是一个例子, 如果指定时间内没有获得结果, 就将 Promise 的状态变为reject, 否则变为resolve。
var p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
])
p.then(response => console.log(response))
p.catch(error => console.log(error))
上面代码中, 如果 5 秒之内fetch方法无法返回结果, 变量p的状态就会变为rejected, 从而触发catch方法指定的回调函数。
Promise.reject(reason) 方法也会返回一个新的 Promise 实例, 该实例的状态为rejected。 它的参数用法与Promise.resolve方法完全一致。
var p = Promise.reject(' 出错了 ');
// 等同于
var p = new Promise((resolve, reject) => reject(' 出错了 '))
p.then(null, function(s) {
console.log(s)
});
// 出错了
上面代码生成一个 Promise 对象的实例p, 状态为rejected, 回调函数会立即执行。
finally方法接受一个方法作为参数,这个方法不管promise最终的状态是怎样,都一定会被执行。它与done方法的最大区别, 它接受一个普通的回调函数作为参数, 该函数不管怎样都必须执行。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
Promise 对象的回调链, 不管以then方法或catch方法结尾, 要是最后一个方法抛出错误, 都有可能无法捕捉到( 因为 Promise 内部的错误不会冒泡到全局)。 因此, 我们可以提供一个done方法, 总是处于回调链的尾端, 保证抛出任何可能出现的错误。
Promise.done() 的用法类似 .then() ,可以提供resolved和rejected方法,也可以不提供任何参数,它的主要作用是在回调链的尾端捕捉前面没有被 .catch() 捕捉到的错误
Promise.prototype.done = function(onFulfilled, onRejected) {
this.then(onFulfilled, onRejected)
.catch(function(reason) {
// 抛出一个全局错误
setTimeout(() => {
throw reason
}, 0);
});
};
从上面代码可见, done方法的使用, 可以像then方法那样用, 提供Fulfilled和Rejected状态的回调函数, 也可以不提供任何参数。 但不管怎样, done都会捕捉到任何可能出现的错误, 并向全局抛出。
三.demo
1. 简单登录判断
//是否登录
let checkLogin = function(){
return new Promise(function(resolve,reject){
//flag是否登录成功
let flag =document.cookie.indexOf("userid")>-1?true:false;
if(flag=true){
resolve({
status:0,
result:true
})
}else{
reject("err");
}
})
};
//用户信息
let getUser = () =>{
return new Promise((resolve,reject) =>{
let userInfo={
userid:"1002"
}
resolve(userInfo)
})
}
checkLogin().then((res) =>{
if(res.status == 0){
console.log("login success");
}
}).catch((error)=>{
console.log( `error:${error}` );
});
Promise.all([checkLogin(),getUser()]).then(([res1,res2]) => {
console.log( `输出res1:${res1.result},输出res2:${res2.userid}`);//输出res1:true,输出res2:1002
}).catch((error)=>{
console.log( `error:${error}` );
})
2.加载图片
我们可以将图片的加载写成一个Promise, 一旦加载完成, Promise的状态就发生变化。
const preloadImage = function(path) {
return new Promise(function(resolve, reject) {
var image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};
3. Generator 函数与 Promise 的结合
使用 Generator 函数管理流程, 遇到异步操作的时候, 通常返回一个Promise对象。
function getFoo() {
return new Promise(function(resolve, reject) {
resolve('foo');
});
}
var g = function*() {
try {
var foo = yield getFoo();
console.log(foo);
} catch(e) {
console.log(e);
}
};
function run(generator) {
var it = generator();
function go(result) {
if(result.done) return result.value;
return result.value.then(function(value) {
return go(it.next(value));
}, function(error) {
return go(it.throw(error));
});
}
go(it.next());
}
run(g);
上面代码的 Generator 函数g之中, 有一个异步操作getFoo, 它返回的就是一个Promise对象。 函数run用来处理这个Promise对象, 并调用下一个next方法。