Javascript 中的Promise
一,什么是 Promise
Promise 是异步编程的一种解决方案---回调函数中的回调地狱(callback hell
)。ES6 将其写进了语言标准,统一了用法。简单的说 Promise
就是一个容器,里面保存着某个未来才会结束的事件。
一个 Promise
有以下几种状态:
- pending: 初始状态,既不是成功,也不是失败状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
pending 状态的 Promise 对象可能会变为fulfilled 状态并传递一个值给相应的状态处理方法,也可能变为失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的 then
方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)。
codeacademy中的一个洗碗机的例子就很形象:
- Pending: 洗碗机在工作,但是没有完成清洗流程
- Fulfilled: 洗碗机完成了所有清洗流程,并且装满了干净的餐具
- Rejected: 洗碗机工作过程中出了问题(比如没加洗洁精),结果里面都是不干净的餐具
使用Promise
前先看个简单例子热身:
let myFirstPromise = new Promise(function(resolve, reject){ //当异步代码执行成功时,调用resolve(...), 当异步代码失败时就会调用reject(...) //本例使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法. setTimeout(function(){ resolve("成功!"); //代码正常执行! }, 250); }); myFirstPromise.then(function(successMessage){ //successMessage的值是上面调用resolve(...)方法传入的值. //successMessage参数不一定非要是字符串类型 console.log("Yay! " + successMessage); });
二,Promise
的使用
1,使用约定
- 在 本轮事件循环(event loop)运行完成之前,回调函数是不会被调用的
- 通过
then()
添加的回调函数总会被调用,即便它是在异步操作完成之后才被添加的函数 - 通过多次调用
then()
,可以添加多个回调函数,它们会按照插入顺序一个接一个独立执行
因此,Promise 最直接的好处就是链式调用(chaining)。
2,链式调用
在ES6之前,多重的异步操作,会导致经典的回调地狱:
doSomething(function(result) { doSomethingElse(result, function(newResult) { doThirdThing(newResult, function(finalResult) { console.log('Got the final result: ' + finalResult); }, failureCallback); }, failureCallback); }, failureCallback);
ES6之后,可以把回调绑定到被返回的 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);
注意:一定要有返回值,否则,callback 将无法获取上一个 Promise 的结果。
在 ECMAScript 2017 标准的 async/await
语法糖中,使得逻辑更加的清晰
async function foo() { try { let result = await doSomething(); let newResult = await doSomethingElse(result); let finalResult = await doThirdThing(newResult); console.log(`Got the final result: ${finalResult}`); } catch(error) { failureCallback(error); } }
这跟我之前使用Python
中的异常捕获有着异曲同工之妙。
3,组合使用
Promise.resolve()
]和 Promise.reject()
是手动创建一个已经 resolve 或者 reject 的Promise
的快捷方法[Promise.all()
和 [Promise.race()
是并行运行异步操作的两个组合式工具。
比如:
Promise.all([func1(), func2(), func3()]) .then(([result1, result2, result3]) => { /* use result1, result2 and result3 */ });
发起并行操作,然后等多个操作全部结束后进行下一步操作。
4,关于时序
即使是一个已经变成 resolve 状态的 Promise
,传递给 then()
的函数也总是会被异步调用。传递到 then()
中的函数被置入了一个微任务队列,而不是立即执行,这意味着它是在 JavaScript 事件队列的所有运行时结束了,事件队列被清空之后,才开始执行:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); wait().then(() => console.log(4)); Promise.resolve().then(() => console.log(2)).then(() => console.log(3)); console.log(1); // 1, 2, 3, 4
三,Promise
的特点小结
- 对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。 - 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。