Promise
Promise对象用于表示一个异步操作的最终完成(或失败)及其结果值
描述
一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知的值。它让您能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。
一个 Promise 必然处于以下几种状态之一:
- 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled): 意味着操作成功完成。
- 已拒绝(rejected): 意味着操作失败。
待定状态的 Promise 对象要么会通过一个值被兑现(fulfilled),要么会通过一个原因(错误)被拒绝(rejected)。当这些情况之一发生时,我们用 promise 的 then 方法排列起来的相关处理程序就会被调用。如果 promise 在一个相应的处理程序被绑定时就已经被兑现或被拒绝了,那么这个处理程序就会被调用,因此在完成异步操作和绑定处理方法之间不会存在竞争状态。
因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回的是 promise, 所以它们可以被链式调用。
不要和惰性求值混淆: 有一些语言中有惰性求值和延时计算的特性,它们也被称为“promises”,例如 Scheme。JavaScript 中的 promise 代表的是已经正在发生的进程, 而且可以通过回调函数实现链式调用。 如果您想对一个表达式进行惰性求值,就考虑一下使用无参数的"箭头函数": f = () =>表达式 来创建惰性求值的表达式,使用 f() 求值。
为什么使用Promise
本质上 Promise 是一个函数返回的对象,我们可以在它上面绑定回调函数,这样我们就不需要在一开始把回调函数作为参数传入这个函数了。
假设现在有一个名为 functionWithoutPromise() 的函数,它接收一些配置和两个回调函数,然后异步执行任务。一个回调函数在任务成功执行时被调用,另一个则在出现异常时被调用。
以下为使用 functionWithoutPromise() 的示例:
function functionWithoutPromise(taste,successCallback,failedCallback){
//任务执行
//.......
//任务执行成功
let result = 1
//任务执行失败
//result = 0
if (result == 1){
successCallback()
}else {
failedCallback()
}
}
functionWithoutPromise('taste', successCallback, failureCallback)
这里我们通过Promise改写
function functionReturnPromise(taste){
return new Promise((resolve, reject)=>{
resolve(taste)
})
}
functionReturnPromise('taste').then(successCallback,failureCallback)
我们把这个称谓异步函数调用,这种形式有若干优点,下面我们将会逐一讨论。
注意:then()方法有两个参数,第一个参数表示当promise状态为fulfilled时调用的函数,第二个参数表示当promise状态为rejected时调用的参数。详情见后文resolve和reject详解。
约定
不同于“老式”的传入回调,在使用 Promise 时,会有以下约定:
- 在本轮 事件循环 运行完成之前,回调函数是不会被调用的。
- 即使异步操作已经完成(成功或失败),在这之后通过 then() 添加的回调函数也会被调用。
- 通过多次调用 then() 可以添加多个回调函数,它们会按照插入顺序进行执行。
Promise 很棒的一点就是链式调用(chaining)。
由于 then 和 Promise.prototype.catch() 方法都会返回 promise,它们可以被链式调用——这同时也是一种被称为复合( composition) 的操作。
创建Promise
const myFirstPromise = new Promise((resolve, reject) => {
// ?做一些异步操作,最终会调用下面两者之一:
//
// resolve(someValue); // fulfilled
// ?或
// reject("failure reason"); // rejected
});
Promise的构造函数接收一个参数,是函数,并且传入两个参数,resolve和reject,分别表示异步执行成功后的回调函数和异步执行失败后的回调函数。
const promise1 = new Promise((resolve, reject) => {
resolve('Success!');
});
promise1.then((value) => {
console.log(value);
// expected output: "Success!"
});
注意:如果忽略针对某个状态的回调函数参数,或者提供非函数 (nonfunction) 参数,那么 then 方法将会丢失关于该状态的回调函数信息,但是并不会产生错误。如果调用 then 的 Promise 的状态(fulfillment 或 rejection)发生改变,但是 then 中并没有关于这种状态的回调函数,那么 then 将创建一个没有经过回调函数处理的新 Promise 对象,这个新 Promise 只是简单地接受调用这个 then 的原 Promise 的终态作为它的终态。
链式调用
then() 函数会返回一个和原来不同的新的 Promise:
const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);
或者:
const promise2 = doSomething().then(successCallback, failureCallback);
promise2 不仅表示 doSomething() 函数的完成,也代表了你传入的 successCallback 或者 failureCallback 的完成,这两个函数也可以返回一个 Promise 对象,从而形成另一个异步操作,这样的话,在 promise2 上新增的回调函数会排在这个 Promise 对象的后面。
基本上,每一个 Promise 都代表了链中另一个异步过程的完成。
在过去,要想做多重的异步操作,会导致经典的回调地狱:
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
现在,我们可以把回调绑定到返回的 Promise 上,形成一个 Promise 链:
doSomething().then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
注意,catch()和then()一样,会形成一个新的Promise,所以,如果catch()之后的其他then()会继续执行:
new Promise((resolve, reject) => {
console.log('初始化');
resolve();
})
.then(() => {
throw new Error('某个错误');
console.log('执行「这个」');
})
.catch(() => {
console.log('执行「那个」');
})
.then(() => {
console.log('执行「最后一个」,无论前面发生了什么');
});
输出:
初始化
执行“那个”
执行“最后一个”,无论前面发生了什么
因为抛出了某个错误,所以“执行「这个」”不会输出,catch()之后的then()继续执行。
同样的,错误也会顺着Promise链向下传递。
doSomething()
.then(result => doSomethingElse(result))//1
.then(newResult => doThirdThing(newResult))//2
.then(finalResult => console.log(`Got the final result: ${finalResult}`))//3
.catch(failureCallback);
也就是说,如果在第1行出现异常,第2,3行就不会执行,异常会随着promise链向下传播直到被catch()捕获。和try……catch……类似:
try {
let result = syncDoSomething();
let newResult = syncDoSomethingElse(result);
let finalResult = syncDoThirdThing(newResult);
console.log(`Got the final result: ${finalResult}`);
} catch(error) {
failureCallback(error);
}
resolve()和reject()
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
当我们在excutor函数中调用resolve方法时,Promise的状态就变成fulfilled,即操作成功状态,还记得上面Promise.prototype上面的then和catch方法吗?当Promise状态为fullfilled状态时执行then方法里的操作,注意了,then方法里面有两个参数onfulfilled(Promise为fulfilled状态时执行) 和onrejected(Promise为rejected状态时执行),步骤如下:
- 实例化Promise(new Promise(function(resolve,reject)))
- 用Promise的实例调用then方法。
// 成功的回调函数
function successCallback(result) {
console.log("任务执行成功");
}
// 失败的回调函数
function failureCallback(error) {
console.log("运行错误");
}
function functionReturnPromise(taste){
return new Promise((resolve, reject)=>{
reject(taste)
})
}
functionReturnPromise('taste').then(successCallback,failureCallback)
输出结果:
运行错误
简单的理解就是调用reject方法,Promise变为操作成功状态(rejected),执行then方法里面onrejected里的操作。其实then里面的函数就是我们平时所说的回调函数,只不过在这里只是把它分离出来而已。
我们注意到除了then方法外,Promise原型上还有另外一个叫catch的方法,那么这个方法的作用是什么呢?其实跟then方法中的第二个参数一样,就是在Promise状态为rejected时执行,then方法捕捉到Promise的状态为rejected,就执行catch方法里面的操作,下面用catch方法改写上面reject用法里面的例子,如下所示:
function functionReturnPromise(taste){
return new Promise((resolve, reject)=>{
reject(taste)
})
}
functionReturnPromise('taste').catch(failureCallback)
注意:虽然异常可以随着promise链向下传递,但是当该异常被处理之后就会形成新的promise,之后的promise的状态不会直接由pending->rejected,观察下面的代码:
function functionReturnPromise(taste){
return new Promise((resolve, reject)=>{
reject(taste)
})
}
functionReturnPromise('taste').then(successCallback,failureCallback).catch(failureCallback2)
也就是说,异常在 then(successCallback,failureCallback) 阶段就已经被捕获处理,此时得到了一个新的promise,状态为fulfilled,之后的catch()并不能捕获到异常。