参考:阮一峰 promise
1.promise含义
promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和强大。
所谓promise,简单说就是一个容器,里面保存着某个未来才会结束的事件的结果。从语法上说,promise是一个对象,从他可以获得异步操作的消息。promise提供统一的API,各种异步操作都可以用同样的方法进行处理。
promise对象有以下两个特点:
(1)对象的状态不受外界影响。promise对象有三种状态:pending、fulfilled、rejected。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是promise这个名字的由来,“承诺”,其他手段无法改变状态。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。promise对象的状态改变只有两种情况:从pending变成fulfilled,从pending变成rejected。只要这两种情况发生,状态就凝固不会再变了,这时就称为resolved。如果改变已经发生,再对promise对象添加回调函数,也会立即得到这个结果。这与事件完全不同。事件的特点是,如果你错过了它,再去监听是得不到结果的。
有了promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调。此外,promise对象提供统一的接口,使得控制异步操作更加容易。
promise也有一些缺点。首先,无法取消promise,一旦新建就会立即执行,无法中途取消。其次,如果不设置回调函数,promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段。
如果某些事件不断的反复发生,使用Stream模式比部署promise更好。???stream是啥???
2.基本用法
promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JS引擎提供。
resolve函数的作用是,将promise对象的状态从pending变为fulfilled,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
reject函数的作用是,将Promise对象的状态从pending变为rejected,在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去。
promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。promise.then(function(val){}, function(err){})
let 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最后输出。
如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例,比如像下面这样。
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作。
注意,这时p1的状态就会传递给p2,也就是说,p2自己的状态无效,由p1的状态决定p2的状态,p2后面的then语句都变成针对p1的。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。
注意,调用resolve或reject并不会终结 Promise 的参数函数的执行。写在resolve或reject之后的语句是会执行的。但是一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
new Promise((resolve, reject) => {
return resolve(1);
// 后面的语句不会执行
console.log(2);
})
3.promise的API
(1) Promise.prototype.then
如果then方法返回的是一个新的promise实例,那可以采用链式写法,then方法后再调用一个then方法。
(2) Promise.prototype.catch
catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 then方法中回调函数运行时发生的错误
console.log('发生错误!', error);
});
一般来说,不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。
跟传统的try/catch代码块不同的是,如果没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。
(3)Promise.prototype.finally
finally方法用于指定不管promise对象最后状态如何,都会执行的操作。finally的回调函数不接受任何参数,里面的操作应该是与状态无关的。
(4)Promise.all()
Promise.all()用于将多个Promise实例,包装成一个新的Promise实例。
const p = Promise.all([p1, p2, p3]);
它接受一个数组[p1,p2,p3]作为参数,数组内都是promise实例,如果不是就会先调用promise.resolve方法将其转为promise实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p的状态由p1、p2、p3决定,分成两种情况。
- 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
- 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
(5)Promise.race()
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为reject,否则变为resolve。
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);
上面代码中,如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。
(6) Promise.allSettled()
Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。
有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,Promise.allSettled()方法就很有用。
(7)Promise.any() 还是提案
只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。该方法目前是一个第三阶段的提案 。
Promise.any()跟Promise.race()方法很像,只有一点不同,就是不会因为某个 Promise 变成rejected状态而结束。
(8)Promise.resolve()
将现有对象转为promise对象。
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
将 jQuery 生成的deferred对象,转为一个新的 Promise 对象。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
Promise.resolve方法的参数分成四种情况。
- 如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
- 参数是一个thenable对象(具有then方法的对象),会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
- 参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
- 不带参数,直接返回一个resolved状态的 Promise 对象。
需要注意的是,立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
setTimeout(function () { //在下一轮“事件循环”开始时执行
console.log('three');
}, 0);
Promise.resolve().then(function () { //在本轮“事件循环”结束时执行
console.log('two');
});
console.log('one');
// one
// two
// three
(9) Promise.reject()
返回一个新的 Promise 实例,该实例的状态为rejected。
注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。
(10)Promise.try() ---还是提案
让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API .
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
4.promise的应用
(1)加载图片
将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化。
(2) generator函数与promise的结合
使用 Generator 函数管理流程,遇到异步操作的时候,通常返回一个Promise对象。