JaveScript 中的 Promise
Promise的含意
所谓的 "promise" 代表为一个容器,保存着某个未来才会结束的事件(非同步)的结果。
Promise有两个特点 :
1.不受外界影响。Promise有三种状态: "pending(进行中)", "fulfilled(已成功)" , "rejected(以失败)",只有非同步操作的结果才能改变他的状态,其他手段无法改变。
2.状态一旦改变就不会再变化。Promise状态有两种改变 : pending -> fulfilled , pending -> rejected。只要任何一种情况发生就不会再发生变化,此时称为 "resolved(已完成)" ,就算再对她添加callback function也依然会得到这个结果。
基本用法 :
const promise = new Promise(function(resolve,rejected){ if(/* Success */) { resolve(value); } else { reject(error); }});
Promise接收一个function作为输入参数,该function的两个参数分别是 "resolve" , "reject"
resolve : 当promise从未完成变成成功(pending -> resolved)时调用,将操作的结果作为参数传递。
rejuct : 当promise从未完成变成失败(pending -> reject)时调用,将操作失败的错误报出并传递。
Promise生成后可以使用.then方法分别指定resolved与reject的callback function
promise.then(function(value){ //Success},function(error){ //false});
then可以接受两个callback function作为参数,当promise变为resolved时呼叫第一个function,反之当变成reject时呼叫第二个function。
Example :
timeout = (ms) => { return new Promise((resolve,reject) =>{ //将"done"当作参数传递给resolve setTimeout(resolve,ms,"done"); });}//传递给resolve的参数因为操作成功所以可以适用then读取到参数(done)timeout(100).then((value) => { console.log(value); //done});
若呼叫resolve或是reject时带有参数,那么他们的参数会被传递给callback function。reject的参数通常是error对象,表示发生的错误 ; resolve的参数除了正常的数值之外已可能是另一个promise
const p1 = new Promise(function (resolve, reject) { // ...});const p2 = new Promise(function (resolve, reject) { // ... resolve(p1);});
p1与p2都是promise,但是p2的resolve将p1做为参数,即一个非同步的操作会return另一个非同步的操作,这时p1的状态会传递给p2,也就是说p1决定了p2的状态,若p1的状态是pending,则p2会等待p1的状态改变;若p1已经是resolved或是rejected,那么p2的callback function便会立刻执行。
const p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('fail')), 3000)})const p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000)})p2 .then(result => console.log(result)) .catch(error => console.log(error))// Error: fail
p1三秒后会变成reject;p2状态在一秒之后会改变,resolve方法返回的是p1,由于p2返回的是另一个promise会导致自己的状态无效,由p1的状态决定了p2的状,所以后面的.then都会变成针对 "p1" 的状态,所以会触发.catch console出Error:fail。
注意!! 若是调用了resolve或reject并不会结束promise的function执行。
new Promise((resolve, reject) => { resolve(1); console.log(2);}).then(r => { console.log(r);});
若是调用了resolve(1)后,后面的console.log(2)仍然会执行并会先被console出来,因为callback function必须等到stack空了后才会执行,所以一般来说后续的操作应该放在.then里面执行,在他们之前加上"return"以防有意外。
new Promise((resolve, reject) => { return resolve(1); // 后面不会执行 console.log(2);})
Promise.prototype.then()
为promise添加状态改变时的callback function,第一个参数是resolved状态的callback function,第二的参数(可选)是rejected状态的callback function。
then会return一个新的promise(不是原来的promise),因此可以採用链式的写法,then后面再调用一个then
function timeout(ms) { return new Promise((resolve, reject) => { resolve(1); });}timeout(1000).then((value) => { console.log(value); return value + 10; // 将第一个callback的结果return给第二的callback当作输入参数}).then((value) => { console.log(value);});
Promise.prototype.catch()
catch用于指定发生错误时的callback function,若一个Promise的非同步操作发生错误状态变为rejected,则会调用catch所指定的callback function处理这个错误,若是then方法指定的callback function执行发生错误的话也会被catch捕获到。
promise发生错误 :
function timeout(ms) { return new Promise((resolve, reject) => { resolve(AAA); //产生error });}timeout(1000).then((value) => { console.log("Success");}).catch((error) =>{ console.log(error) //ReferenceError: AAA is not defined})
then发生错误 :
function timeout(ms) { return new Promise((resolve, reject) => { resolve(1); });}timeout(1000).then((value) => { console.log(X); //造成错误}).catch((error) =>{ console.log(error) //ReferenceError: X is not defined})
由于catch可以捕获到promise与之后then的错误,所以建议对于reject来说使用catch,而不使用then的第二个参数。
Promise.prototype.finally()
finally用于不管Promise最后状况为何都会执行的操作。(ES2018加入)
promise.then(result => {...}).catch(error => {...}).finally(() => {...});
不论promise的状态为何都会执行finally指定的callback function。
由于finally不接受任何输入参数,所以无法知道promise的状态为何,这鳔式finally里面的操作式与状态无关的,不依赖promise的执行结果。
Promise.prototype.all()
all用于将多个Promise包装成一个新的Promise。
const p = Promise.all([p1, p2, p3]);
p的状态由p1,p2,p3决定,分成两种情况 :
1.当p1,p2,p3都变成fulfilled时,p才会是fulfilled,此时p1,p2,p3返回一个数组传递给p的callback function。
2.只要p1,p2,p3其中一个变成reject那么p就会变成reject,此时第一个变成reject的的return会传递给P的callback funciton。
Example :
const promises = [2, 3, 5, 7, 11, 13].map(function (id) { return getJSON('/post/' + id + ".json");});Promise.all(promises).then(function (posts) { // ...}).catch(function(reason){ // ...});
只有当6个promise的状态都改变成fulfilled后,p的状态才会改变为fulfilled,而若6个promise有其中一个变为reject那p的状态就会变为reject。而当p的状态改变后才会调用后面的callback function。
注意!!如果做为餐数的promise有定义自己的catch那么一旦它变成rejected,并不会触发到all.catch而是自己的catch
const p1 = new Promise((resolve, reject) => { resolve('hello');}).then(result => result).catch(e => e); //定义自己的catchconst p2 = new Promise((resolve, reject) => { throw new Error('报错');}).then(result => result).catch(e => e); //定义自己的catchPromise.all([p1, p2]).then(result => console.log(result)).catch(e => console.log(e));
上面的程式中,p1状态会变为resolved而p2的状态会变为reject而进入自己的catch,当p2进入自己的catch后,由于执行完catch后状态会变为resolved,所以对all而言两个promise都是resolved,便不会进入到all.catch指定的callback function中。
Promise.prototype.race()
同样是将多个promise包装成一个新的promise但与all不同。
const p = Promise.race([p1, p2, p3]);
当p1,p2,p3任何一个发生状态改变时,p的状态就会跟着改变,首先改变状态的promise会return一个参数传入p的callback function中。
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);
上面的程式,若秒之内fatch方法无法返回结果,p的状态就会变成reject(当一个状态改变时,p状态会跟着改变),从而触发catch中的callback function。
Promise.prototype.resolve()
将现有的对象转变为promise
Promise.resolve('foo')// 等价于new Promise(resolve => resolve('foo'))
resolve()的参数有四种 :
1.参数是一个promise : 若promise的参数是一个promise,则不做动作 return 一个promise
2.参数是一个thenable(具有then方法的object)
let thenable = { then: function(resolve, reject) { resolve(42); }};
pormise.resolve会将这个object转为peomise,然后立刻执行thenable的then
let thenable = { then: function(resolve, reject) { resolve(42); }};let p1 = Promise.resolve(thenable);p1.then(function(value) { console.log(value); // 42});
上面的程式中,thanable object的then执行后,p1状态就变为resolved,从而执行p1的then的callback function并console出42。
3.参数不具有then的object或根本不是object,则promise.resolve返回一个新的promise状态为resolved
4.不带任何参数,会直接返回一个resolved状态的promise,注意!! resolve()的promise是在本轮event loop结束时才执行,而不是下一轮event loop才执行
setTimeout(function () { console.log('three');}, 0);Promise.resolve().then(function () { console.log('two');});console.log('one');// one// two// three
由于setTimeout在下一轮event loop才执行,而promise.resolved()是在本轮的event loop结束时执行,console.log("one")则是直接放入stack中立刻执行,所以最先输出。
Promise.prototype.reject()
Promise.reject(reason)会return一个新的promise,而该promise的状态为rejected。
注意!!Promise.reject的参数会做完reject的理由,变成后续catch的参数。
const thenable = { then(resolve, reject) { reject('出错了'); }};Promise.reject(thenable).catch(e => { console.log(e === thenable) console.log(e); console.log(thenable);})/************ true { then: [Function: then] } { then: [Function: then] }*************/
参考资料 :
ECMAScript 6 入门