期约状态机 Promise可以通过 new 操作符来实例化。创建新期约时需要传入 执行器(executor)函数作为参数
1 2 3 let  p = new  Promise (() =>  {});setTimeout (console .log, 0 , p); 
 
在把一个期约实例传给 console.log()时,控制台输出(可能因浏览器不同而略有差异)表明该实例处于待定(pending)状态。如前所述,期约是一个有状态的对象,可能处于如下 3 种状态之一:
待定 (pending)是期约的最初始状态。在待定状态下,期约可以落定(settled)为代表成功的兑现(fulfilled)状态,或者代表失败的拒绝(rejected)状态。 无论落定为哪种状态都是不可逆的。只要从待定转换为兑现或拒绝,期约的状态就不再改变。而有时候也称为“解决”,resolved)
 
兑现 (fulfilled)每个期约只要状态切换为兑现,就会有一个私有的内部值(value)
 
拒绝 (rejected)如果期约被拒绝,程序就会期待期约状态改变时可以拿到拒绝的理由
期约用途 期约主要有两大用途。首先是抽象地表示一个异步操作。期约的状态代表期约是否完成。某些情况下,这个状态机就是期约可以提供的最有用的信息。
 
 
通过执行函数控制期约状态 期约的状态是私有的,所以只能在内部进行操作。内部操作在期约的执行器函数中完成。执行器函数主要有两项职责:初始化期约的异步行为和控制状态的最终转换 。其中,控制期约状态的转换是 通过调用它的两个函数参数实现的。这两个函数参数通常都命名为 resolve()和 reject()。调用resolve()会把状态切换为兑现,调用 reject()会把状态切换为拒绝。另外,调用 reject()也会抛出错误
1 2 3 4 5 6 let  p1=new  Promise ((resolve,reject )=> resolve());setTimeout (console .log,0 ,p1);let  p2 = new  Promise ((resolve, reject ) =>  reject());setTimeout (console .log, 0 , p2); 
 
在前面的例子中,并没有什么异步操作,因为在初始化期约时,执行器函数已经改变了每个期约的状态,执行器函数是同步执行的。这是因为执行器函数是期约的初始化程序。
添加 setTimeout 可以推迟切换状态:
1 2 3 4 let  p = new  Promise ((resolve, reject ) =>  setTimeout (resolve, 1000 ));setTimeout (console .log, 0 , p); 
 
无论 resolve()和 reject()中的哪个被调用,状态转换都不可撤销了。于是继续修改状态会静默失败,如下所示:
1 2 3 4 5 6 let  p = new  Promise ((resolve, reject ) =>  {resolve(); reject();  }); setTimeout (console .log, 0 , p); 
 
Promise.resolve() 下面两个期约实例实际上是一样的:
let p1 = new Promise((resolve, reject) => resolve()); let p2 = Promise.resolve();
 
这个解决的期约的值对应着传给 Promise.resolve()的第一个参数。使用这个静态方法,实际上可以把任何值都转换为一个期约
1 2 3 4 5 6 7 8 setTimeout (console .log,0 ,Promise .resolve());setTimeout (console .log, 0 , Promise .resolve(3 ));setTimeout (console .log, 0 , Promise .resolve(4 , 5 , 6 ));
 
对这个静态方法而言,如果传入的参数本身是一个期约,那它的行为就类似于一个空包装。因此,Promise.resolve()可以说是一个幂等方法
1 2 3 4 5 6 7 8 9 10 11 let  p = Promise .resolve(7 );setTimeout (console .log, 0 , p === Promise .resolve(p));setTimeout (console .log, 0 , p === Promise .resolve(Promise .resolve(p)));let  p = new  Promise (() =>  {});setTimeout (console .log, 0 , p); setTimeout (console .log, 0 , Promise .resolve(p)); setTimeout (console .log, 0 , p === Promise .resolve(p)); 
 
注意,这个静态方法能够包装任何非期约值,包括错误对象,并将其转换为解决的期约。因此,也可能导致不符合预期的行为:
1 2 3 4 let  p = Promise .resolve(new  Error ('foo' ));setTimeout (console .log, 0 , p);
 
Promise.reject() 与 Promise.resolve()类似,Promise.reject()会实例化一个拒绝的期约并抛出一个异步错误(这个错误不能通过 try/catch 捕获,而只能通过拒绝处理程序捕获)。下面的两个期约实例实际上是一样的:
let p1 = new Promise((resolve, reject) => reject()); let p2 = Promise.reject();
 
这个拒绝的期约的理由就是传给 Promise.reject()的第一个参数。这个参数也会传给后续的拒绝处理程序:
1 2 3 4 let  p = Promise .reject(3 );setTimeout (console .log, 0 , p); p.then(null , (e ) =>  setTimeout (console .log, 0 , e));  
 
关键在于,Promise.reject()并没有照搬 Promise.resolve()的幂等逻辑。如果给它传一个期约对象,则这个期约会成为它返回的拒绝期约的理由:
1 2 3 setTimeout (console .log, 0 , Promise .reject(Promise .resolve()));
 
同步/异步执行的二元性 1 2 3 4 5 6 7 8 9 10 11 12 try  {throw  new  Error ('foo' );} catch (e) { console .log(e); } try  {Promise .reject(new  Error ('bar' ));} catch (e) { console .log(e);} 
 
第一个 try/catch 抛出并捕获了错误,第二个 try/catch 抛出错误却没有捕获到,这里的同步代码之所以没有捕获期约抛出的错误,是因为它没有通过异步模式捕获错误。从这里就可以看出期约真正的异步特性:它们是同步对象( 在同步执行模式中使用),但也是异步执行模式  的媒介。 在前面的例子中,拒绝期约的错误并没有抛到执行同步代码的线程里,而是通过浏览器异步消息队列来处理的 。因此,try/catch 块并不能捕获该错误。代码一旦开始以异步模式执行,则唯一与之交互 的方式就是使用异步结构——更具体地说,就是期约的方法。
Promise.prototype.then() Promise.prototype.then()是为期约实例添加处理程序的主要方法。这个 then()方法接收最多 两个参数:onResolved 处理程序和 onRejected 处理程序。这两个参数都是可选 的,如果提供的话,则会在期约分别进入“兑现”和“拒绝”状态时执行。传给 then()的任何非函数类型的参数都会被静 默忽略 。如果想只提供 onRejected 参数,那就要在 onResolved 参数的位置上传入 undefined。这 样有助于避免在内存中创建多余的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function  onResolved (id )  {  setTimeout (console .log, 0 , id, 'resolved' );  }  function  onRejected (id )  {  setTimeout (console .log, 0 , id, 'rejected' );  }  let  p1 = new  Promise ((resolve, reject ) =>  setTimeout (resolve, 3000 )); let  p2 = new  Promise ((resolve, reject ) =>  setTimeout (reject, 3000 )); p1.then(() =>  onResolved('p1' ),   () =>  onRejected('p1' ));  p2.then(() =>  onResolved('p2' ),   () =>  onRejected('p2' ));  
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function  onResolved (id )  {  setTimeout (console .log, 0 , id, 'resolved' );  }  function  onRejected (id )  {  setTimeout (console .log, 0 , id, 'rejected' );  }  let  p1 = new  Promise ((resolve, reject ) =>  setTimeout (resolve, 3000 )); let  p2 = new  Promise ((resolve, reject ) =>  setTimeout (reject, 3000 )); p1.then('gobbeltygook' );  p2.then(null , () =>  onRejected('p2' ));  
 
Promise.prototype.then()方法返回一个新的期约实例:
1 2 3 4 5 let  p1 = new  Promise (() =>  {}); let  p2 = p1.then(); setTimeout (console .log, 0 , p1); setTimeout (console .log, 0 , p2); setTimeout (console .log, 0 , p1 === p2); 
 
这个新期约实例基于 onResovled 处理程序的返回值构建。换句话说,该处理程序的返回值会通过 Promise.resolve()包装来生成新期约。如果没有提供这个处理程序,则 Promise.resolve()就会 包装上一个期约解决之后的值。如果没有显式的返回语句,则 Promise.resolve()会包装默认的返回 值 undefined。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 let  p1 = Promise .resolve('foo' ); let  p2 = p1.then();setTimeout (console .log, 0 , p2); let  p3 = p1.then(() =>  undefined ); let  p4 = p1.then(() =>  {}); let  p5 = p1.then(() =>  Promise .resolve()); setTimeout (console .log, 0 , p3); setTimeout (console .log, 0 , p4); setTimeout (console .log, 0 , p5); let  p6 = p1.then(() =>  'bar' ); let  p7 = p1.then(() =>  Promise .resolve('bar' )); setTimeout (console .log, 0 , p6); setTimeout (console .log, 0 , p7); let  p8 = p1.then(() =>  new  Promise (() =>  {})); let  p9 = p1.then(() =>  Promise .reject()); setTimeout (console .log, 0 , p8); setTimeout (console .log, 0 , p9);   let  p10 = p1.then(() =>  { throw  'baz' ; }); setTimeout (console .log, 0 , p10); ...  let  p11 = p1.then(() =>  Error ('qux' )); setTimeout (console .log, 0 , p11); 
 
onRejected 处理程序也与之类似:onRejected 处理程序返回的值也会被 Promise.resolve() 包装 。乍一看这可能有点违反直觉,但是想一想,onRejected 处理程序的任务不就是捕获异步错误吗? 因此,拒绝处理程序在捕获错误后不抛出异常是符合期约的行为,应该返回一个解决期约
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 let  p1 = Promise .reject('foo' ); let  p2 = p1.then(); setTimeout (console .log, 0 , p2); let  p3 = p1.then(null , () =>  undefined ); let  p4 = p1.then(null , () =>  {}); let  p5 = p1.then(null , () =>  Promise .resolve()); setTimeout (console .log, 0 , p3); setTimeout (console .log, 0 , p4); setTimeout (console .log, 0 , p5); let  p6 = p1.then(null , () =>  'bar' ); let  p7 = p1.then(null , () =>  Promise .resolve('bar' )); setTimeout (console .log, 0 , p6); setTimeout (console .log, 0 , p7); let  p8 = p1.then(null , () =>  new  Promise (() =>  {})); let  p9 = p1.then(null , () =>  Promise .reject()); setTimeout (console .log, 0 , p8); setTimeout (console .log, 0 , p9); let  p10 = p1.then(null , () =>  { throw  'baz' ; }); setTimeout (console .log, 0 , p10); let  p11 = p1.then(null , () =>  Error ('qux' )); setTimeout (console .log, 0 , p11); 
 
Promise.prototype.catch() Promise.prototype.catch()方法用于给期约添加拒绝处理程序 。这个方法只接收一个参数: onRejected 处理程序。事实上,这个方法就是一个语法糖,调用它就相当于调用 Promise.prototype.  then(null, onRejected) 
1 2 3 4 5 6 7 let  p = Promise .reject(); let  onRejected = function (e )  {  setTimeout (console .log, 0 , 'rejected' );  };  p.then(null , onRejected);  p.catch(onRejected);  
 
Promise.prototype.catch()返回一个新的期约实例:
1 2 3 4 5 let  p1 = new  Promise (() =>  {}); let  p2 = p1.catch(); setTimeout (console .log, 0 , p1); setTimeout (console .log, 0 , p2); setTimeout (console .log, 0 , p1 === p2); 
 
Promise.prototype.finally() Promise.prototype.finally()方法用于给期约添加 onFinally 处理程序,这个处理程序在期 约转换为解决或拒绝状态时都会执行。这个方法可以避免 onResolved 和 onRejected 处理程序中出 现冗余代码。但 onFinally 处理程序没有办法知道期约的状态是解决还是拒绝,所以这个方法主要用清理代码
1 2 3 4 5 6 7 let  p1 = Promise .resolve(); let  p2 = Promise .reject(); let  onFinally = function ( )  {  setTimeout (console .log, 0 , 'Finally!' )  }  p1.finally(onFinally);  p2.finally(onFinally);  
 
Promise.prototype.finally()方法返回一个新的期约实例:
1 2 3 4 5 let  p1 = new  Promise (() =>  {}); let  p2 = p1.finally();setTimeout (console .log, 0 , p1); setTimeout (console .log, 0 , p2); setTimeout (console .log, 0 , p1 === p2); 
 
这个新期约实例不同于 then()或 catch()方式返回的实例。因为 onFinally 被设计为一个状态 无关的方法,所以在大多数情况下它将表现为父期约的传递 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 let  p1 = Promise .resolve('foo' ); let  p2 = p1.finally(); let  p3 = p1.finally(() =>  undefined ); let  p4 = p1.finally(() =>  {}); let  p5 = p1.finally(() =>  Promise .resolve()); let  p6 = p1.finally(() =>  'bar' ); let  p7 = p1.finally(() =>  Promise .resolve('bar' )); let  p8 = p1.finally(() =>  Error ('qux' )); setTimeout (console .log, 0 , p2); setTimeout (console .log, 0 , p3); setTimeout (console .log, 0 , p4); setTimeout (console .log, 0 , p5); setTimeout (console .log, 0 , p6); setTimeout (console .log, 0 , p7); setTimeout (console .log, 0 , p8); let  p9 = p1.finally(() =>  new  Promise (() =>  {})); let  p10 = p1.finally(() =>  Promise .reject()); setTimeout (console .log, 0 , p9); setTimeout (console .log, 0 , p10); let  p11 = p1.finally(() =>  { throw  'baz' ; }); setTimeout (console .log, 0 , p11); 
 
非重入期约方法 当期约进入落定状态 时,与该状态相关的处理程序仅仅会被排期 ,而非立即执行。跟在添加这个处 理程序的代码之后的同步代码 一定会在处理程序之前先执行。即使期约一开始就是与附加处理程序关联 的状态,执行顺序也是这样的。这个特性由 JavaScript 运行时保证,被称为“非重入”(non-reentrancy) 特性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let  synchronousResolve; let  p = new  Promise ((resolve ) =>  {  synchronousResolve = function ( )  {   console .log('1: invoking resolve()' );   resolve();   console .log('2: resolve() returns' );   };  });  p.then(() =>  console .log('4: then() handler executes' ));  synchronousResolve();  console .log('3: synchronousResolve() returns' ); 
 
在这个例子中,即使期约状态变化发生在添加处理程序之后,处理程序也会等到运行的消息队列让 它出列时才会执行。
传递解决值和拒绝理由 到了落定状态后,期约会提供其解决值(如果兑现)或其拒绝理由(如果拒绝)给相关状态的处理 程序。拿到返回值后,就可以进一步对这个值进行操作。比如,第一次网络请求返回的 JSON 是发送第 二次请求必需的数据,那么第一次请求返回的值就应该传给 onResolved 处理程序继续处理。当然,失 败的网络请求也应该把 HTTP 状态码传给 onRejected 处理程序。
在执行函数中,解决的值和拒绝的理由是分别作为 resolve()和 reject()的第一个参数往后传 的。然后,这些值又会传给它们各自的处理程序,作为 onResolved 或 onRejected 处理程序的唯一 参数。
1 2 3 4 let  p1 = new  Promise ((resolve, reject ) =>  resolve('foo' )); p1.then((value ) =>  console .log(value));  let  p2 = new  Promise ((resolve, reject ) =>  reject('bar' )); p2.catch((reason ) =>  console .log(reason));  
 
then()和 catch()的 onRejected 处理程序在语义上相当于 try/catch。出发点都是捕获错误之 后将其隔离,同时不影响正常逻辑执行。为此,onRejected 处理程序的任务应该是在捕获异步错误之 后返回一个解决的期约。下面的例子中对比了同步错误处理与异步错误处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 console .log('begin synchronous execution' ); try  {  throw  Error ('foo' );  } catch (e) {   console .log('caught error' , e);  }  console .log('continue synchronous execution' ); new  Promise ((resolve, reject ) =>  {  console .log('begin asynchronous execution' );   reject(Error ('bar' ));  }).catch((e ) =>  {   console .log('caught error' , e);  }).then(() =>  {   console .log('continue asynchronous execution' );  });  
 
期约连锁 每个后续的处理程序都会等待前一个期约解决,然后实例化一个新期约并返回它。这种结构可以简 洁地将异步任务串行化,解决之前依赖回调的难题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function  delayedResolve (str )  {  return  new  Promise ((resolve, reject ) =>  {   console .log(str);   setTimeout (resolve, 1000 );   });  } delayedResolve('p1 executor' )   .then(() =>  delayedResolve('p2 executor' ))   .then(() =>  delayedResolve('p3 executor' ))   .then(() =>  delayedResolve('p4 executor' ))  
 
Promise.all() Promise.all方法用于将多个 Promise 实例,这个静态方法接收一个可迭代对象,将参数转为 Promise 实例,再包装成一个新的 Promise 实例。
合成的期约只会在每个包含的期约都解决之后才解决 
如果至少有一个包含的期约待定,则合成的期约也会待定。如果有一个包含的期约拒绝,则合成的 期约也会拒绝 
 
1 2 3 4 5 6 7 let  p1=Promise .all([new  Promise (()=> {})]);setTimeout (console .log,0 ,p1);let  p2=Promise .all([Promise .resolve(),Promise .reject(),Promise .resolve()]);setTimeout (console .log,0 ,p2);
 
如果所有期约都成功解决,则合成期约的解决值就是所有包含期约解决值的数组,按照迭代器顺序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let  p = Promise .all([  Promise .resolve(3 ),   Promise .resolve(),   Promise .resolve(4 )  ]);  p.then((values ) =>  setTimeout (console .log, 0 , values));  let  p = Promise .all([  Promise .reject(3 ),   new  Promise ((resolve, reject ) =>  setTimeout (reject, 1000 ))  ]);  p.catch((reason ) =>  setTimeout (console .log, 0 , reason));  
 
Promise.race() Promise.race()静态方法返回一个包装期约,是一组集合中最先解决或拒绝的期约的镜像。这个 方法接收一个可迭代对象,返回一个新期约
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 let  p1 = Promise .race([  Promise .resolve(3 ),   new  Promise ((resolve, reject ) =>  setTimeout (reject, 1000 ))  ]);  setTimeout (console .log, 0 , p1); let  p2 = Promise .race([  Promise .reject(4 ),   new  Promise ((resolve, reject ) =>  setTimeout (resolve, 1000 ))  ]);  setTimeout (console .log, 0 , p2); let  p3 = Promise .race([  Promise .resolve(5 ),   Promise .resolve(6 ),   Promise .resolve(7 )  ]);  setTimeout (console .log, 0 , p3); 
 
串行期约合成 1 2 3 4 5 6 7 function  addTwo (x ) {return  x+1 ;}function  addThree (x ) {return  x+3 ;}function  addFive (x ) {return  x+5 ;}function  addTen (x ) {    return  [addTwo,addThree,addFive].reduce((promise,fn )=> promise.then(fn),Promise .resolve(x)); } addTen(8 ).then(console .log); 
 
done Promise 对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为 Promise 内部的错误不会冒泡到全局)。因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 asyncFunc()   .then(f1)   .catch(r1)   .then(f2)   .done(); Promise .prototype.done=function (onResolved,onRejected ) {        this .then(onResolved,onRejected)             .catch(function (reason ) {                                  setTimeout (()=> {throw  reason},0 );             });                 } 
 
finally finally()f方法用于指定不管Promise对象最后状态如何,都会执行的操作,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。
1 2 3 4 5 6 7 8 9 10 11 12 server.listen(0 )   .then(function  ( )  {        })   .finally(server.stop); 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})         );     } 
 
用途 将图片的加载写成一个Promise,一旦加载完成,Promise的状态发生变化
1 2 3 4 5 6 7 8 9     const  preloadImage=function (path ) {         return  new  Promise (function (resolve,reject ) {             const  image=new  Image();             image.onload=resolve;             image.onerror=reject;             image.src=path;         })     }