Promise对象:异步编程
Promise 表示一个异步操作的最终结果。以下几个术语的含义。
- 解决(fulfill):指一个 promise 成功时进行的一系列操作,如状态的改变、回调的执行。虽然规范中用
fulfill来表示解决,但在后世的 promise 实现多以resolve来指代之。 - 拒绝(reject):指一个 promise 失败时进行的一系列操作。
- 终值(eventual value):所谓终值,指的是 promise 被解决时传递给解决回调的值,由于 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待态的结束,故称之终值,有时也直接简称为值(value)。
- 据因(reason):也就是拒绝原因,指在 promise 被拒绝时传递给拒绝回调的值。
Promise有三种状态:
pending(进行中)、fulfilled(已成功)和rejected(已失败)
Promise对象有以下两个特点:
(1)对象状态不受外界影响:只有异步操作的结果,可以决定当前的状态。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise对象的状态改变,只有两种可能:
从pending变为fulfilled(resolved) 需携带一个value值。
从pending变为rejected 需携带一个reason值。
当这两种情况发生后,状态定型,再取调用promise对象,也只会得到和之前一样的结果。
Promise也有一些缺点:
-
无法取消状态:一旦新建就会立即执行,无法中途取消。
-
如果不设置回调函数,
Promise内部抛出的错误,不会反应到外部;也就是使用 -
当处于
pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
基础语法:
创建一个promise实例:
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
//resolve: 将结果由pendding转为resolved,即”未完成“转”成功“;
// reject: 将结果由pendding转为rejected,即”未完成“转”失败“
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(function(value) {
// success resolve的回调
}, function(error) {
// failure reject的回调
});
例:一旦promise实例新建,就会被立即执行
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
注意,调用resolve或reject并不会终结 Promise 的参数函数的执行。
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
例:使用promise实现Ajax的功能:
const getJson = function (url) {
const promise = new Promise((resolve, reject) => {
var 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("url.json")
.then(result => {
console.log("success:" + result);
}).catch(error => {
console.log("error:" + error);
});
Promise.prototype.then()
一个 promise 必须提供一个 then 方法以访问其当前值、终值和据因。
promise 的 then 方法接受两个参数:
promise.then(onFulfilled, onRejected)
参数可选
onFulfilled 和 onRejected 都是可选参数。
- 如果
onFulfilled不是函数,其必须被忽略 - 如果
onRejected不是函数,其必须被忽略
onFulfilled 特性
如果 onFulfilled 是函数:
- 当
promise执行结束后其必须被调用,其第一个参数为promise的终值 - 在
promise执行结束前其不可被调用 - 其调用次数不可超过一次
onRejected 特性
如果 onRejected 是函数:
- 当
promise被拒绝执行后其必须被调用,其第一个参数为promise的据因 - 在
promise被拒绝执行前其不可被调用 - 其调用次数不可超过一次
调用时机
onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用
调用要求
onFulfilled 和 onRejected 必须被作为函数调用(即没有 this 值)
多次调用
then 方法可以被同一个 promise 调用多次
- 当
promise成功执行时,所有onFulfilled需按照其注册顺序依次回调 - 当
promise被拒绝执行时,所有的onRejected需按照其注册顺序依次回调
返回
then 方法必须返回一个 promise 对象。**所以可以采用链式写法。**即then方法后面再调用另一个then方法。达到可以将前一个then实例的返回结果作为后一个实例的参数传入。
const promise1 = new Promise((resolve, reject) => {
// dosomething...
})
const promise2= promise1.then(onFulfilled, onRejected);
- 如果
onFulfilled或者onRejected返回一个值x,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x) - 如果
onFulfilled或者onRejected抛出一个异常e,则promise2必须拒绝执行,并返回拒因e - 如果
onFulfilled不是函数且promise1成功执行,promise2必须成功执行并返回相同的值 - 如果
onRejected不是函数且promise1拒绝执行,promise2必须拒绝执行并返回相同的据因
不论
promise1被 reject 还是被 resolve 时promise2都会被 resolve,只有出现异常时才会被 rejected。
Promise 解决过程resolve
Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise 。
这种thenable的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法即可;这同时也使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。
运行 [[Resolve]](promise, x) 需遵循以下步骤:
x 与 promise 相等
如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
x 为 Promise
如果 x 为 Promise ,则使 promise 接受 x 的状态 :
- 如果
x处于等待态,promise需保持为等待态直至x被执行或拒绝 - 如果
x处于执行态,用相同的值执行promise - 如果
x处于拒绝态,用相同的据因拒绝promise
x 为对象或函数
如果 x 为对象或者函数:
- 把
x.then赋值给then - 如果取
x.then的值时抛出错误e,则以e为据因拒绝promise - 如果
then是函数,将x作为函数的作用域this调用之。传递两个回调函数作为参数,第一个参数叫做resolvePromise,第二个参数叫做rejectPromise:- 如果
resolvePromise以值y为参数被调用,则运行[[Resolve]](promise, y) - 如果
rejectPromise以据因r为参数被调用,则以据因r拒绝promise - 如果
resolvePromise和rejectPromise均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用 - 如果调用
then方法抛出了异常e:- 如果
resolvePromise或rejectPromise已经被调用,则忽略之 - 否则以
e为据因拒绝promise
- 如果
- 如果
then不是函数,以x为参数执行promise
- 如果
- 如果
x不为对象或者函数,以x为参数执行promise
如果一个 promise 被一个循环的thenable链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为据因来拒绝 promise
function resolve(promise, x) {
if (x === promise) { //promise和x指向相同的值
return reject(promise, new TypeError("cant be the same"));
} if (isPromise(x)) { // x是一个promise
if (x.state === "pending") {
return x.then(() => {
resolve(promise, x.value);
}, () => {
reject(promise, x.value)
})
} if (x.state === "rejected") {
return reject(promise, x.value)
} if (x.state === "fulfilled") {
return fulfill(promise, x.value);
}
} else if (isObject(x) || isFunction(x)) { // x是一个对象或一个函数
let then;
try {
then = x.then;
} catch (e) {
return reject(promise, x.value)
}
if (isFunction(then)) {
let iscalled = false;
try {
then.call(x, function resolvePromise(y) { //对thenable进行兼容
if (iscalled) {
return;
}
iscalled = true;
resolve(promise, y);
}, function rejectPromise(r) {
if (iscalled) {
return;
}
iscalled = true;
reject(promise, r);
})
} catch (e) {
if (!iscalled) {
return reject(promise, e);
}
}
} else {
return fulfill(promise, x);
}
} else { //x不是对象也不是函数
return fulfill(promise, x);
}
}
示例1
const promise = new Promise((resolve, reject)=>{
console.log(1);
resolve();
console.log(2);
});
promise.then(()=>{
console.log(3);
});
console.log(4);//输出结果 1 2 4 3
示例2
const promise = Promise.resolve(1) //设置为resolve状态,并且值为1
.then(2) // 当传入值不是一个函数, 则返回上一个promise的值1,及状态resolve
.then(Promise.resolve(3)) // 是一个promise,则返回上一个promise的值1,及状态resolve
.then(console.log) //是一个function, 执行function,所以输出1
//输出1
Promise.prototype.catch()
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。, 处理前一个回调函数的reject
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
如果 Promise 状态已经变成**resolved**,再抛出错误是无效的。
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) });
// ok
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个**catch**语句捕获。
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误, 前面三个promise产生的错误都会被这一个catch捕获
});
catch语法捕获后面的then链式语句产生的错误,catch也能抛出错误:
const 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 没有声明, 但是不会被捕获,因为接下来没有catch语句, 也不会传递到外层
y + 2;
}).then(function() {
console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
Promise.prototype.finally()
finally()方法用于指定不管 Promise 对象最后状态如何,最后都会执行的操作。
**finally**方法的回调函数不接受任何参数,无法得知promise的状态, 所以这里买呢的操作一般都是与状态无关的擦偶哦在,不依赖promise执行结果的一些操作。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
和Java的 try catch finally一样
实现:
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
从上面的实现还可以看到,finally方法总是会返回原来的值。
// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})
// resolve 的值是 2
Promise.resolve(2).finally(() => {})
// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})
// reject 的值是 3
Promise.reject(3).finally(() => {})
Promise.all()
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
//p1,p2,p3都是promise实例
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
注意,如果作为参数的 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)) // ["hello", Error: 报错了]
.catch(e => console.log(e));
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));// Error: 报错了
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);
//表示五秒之后还没有结果,就直接返回reject,从而触发catch函数
Promise.allSettled()
Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例
只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。
结束的状态只可能是fulfilled, 可以根据返回的结果对象种的status来判断此promise实例的状态。
该方法返回的新的 Promise 实例,一旦结束,状态总是**fulfilled**,不会变成**rejected**。状态变成fulfilled后,Promise 的监听函数接收到的参数是一个数组。每个成员对应一个传入Promise.allSettled()的 Promise 实例。
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
**常用场景:**有时又对于多个异步请求,我们不关心这些请求的操作结果, 只关心所有的操作是否都完成。
Promise.any()
Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。
只要参数实例有一个变成**fulfilled**状态,包装实例就会变成**fulfilled**状态;
如果所有参数实例都变成**rejected**状态,包装实例就会变成rejected状态;
**Promise.any()**抛出的错误,不是一个一般的错误,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被**rejected**的操作所抛出的错误。
这跟promise.all()相反。
var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);
Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
console.log(result); // 42
});
Promise.any([rejected, alsoRejected]).catch(function (results) {
console.log(results); // [-1, Infinity]
});
Promise.resolve():有时需要将现有对象转为 Promise 对象
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
// 将jquery生成的对象转化为新的promise对象
Promise.resolve方法的参数分成四种情况。
(1)参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
(2)参数是一个thenable对象
thenable对象指的是具有then方法的对象。
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
});
(3)参数不是具有then方法的对象,或根本就不是对象
如果参数是一个原始值,或者是一个不具有then方法的对象,则**Promise.resolve**方法返回一个新的 Promise 对象,状态为resolved。将传入参数作为resolved的结果传出。
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
(4)不带有任何参数:适用于只得到一个promise对象
Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
const p = Promise.resolve();
p.then(function () {
// ...
});
注意:立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
setTimeout(function () {
console.log('three');
}, 0); //在下一轮“事件循环”开始时执行
Promise.resolve().then(function () {
console.log('two');
}); //在本轮“事件循环”结束时执行
console.log('one'); //立即执行
// one
// two
// three
Promise.reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
注意:Promise.reject()方法的参数,会原封不动地作为**reject**的理由,变成后续方法的参数。
const thenable = {
then(resolve, reject) {
reject('出错了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
Promise使用场景
一般使用promise的链式调用可以轻松解决“回调地狱”。
对于回调函数 我们用Jquery的ajax获取数据时 都是以回调函数方式获取的数据
$.get(url, (data) => {
console.log(data)
)
如果说当我们需要发送多个异步请求 并且每个请求之间需要相互依赖 那这时 我们只能 以嵌套方式来解决 形成 "回调地狱"
$.get(url, data1 => {
console.log(data1)
$.get(data1.url, data2 => {
console.log(data2)
$.get(data2.url, data3 => {
console.log(data3)
})
})
})
利用Promise 处理多个相互关联的异步请求
const request = url => {
return new Promise((resolve, reject) => {
$.get(url, data => {
resolve(data)
});
})
};
// 请求data1
request(url).then(data1 => {
return request(data1.url);
}).then(data2 => {
return request(data2.url);
}).then(data3 => {
console.log(data3);
}).catch(err => throw new Error(err));
参考文章:Promise 对象