前言
在之前翻博客时, 看到 promise, 又重读了一边, 突然发现理解很浅, 记的笔记也不是很好理解, 又重新学习 promise, 加深理解, 学以致用
在 promise 出来之前, JS 常用解决异步方式都是采用回调函数方式, 但是如果需求过多, 会形成一系列的回调函数, 俗称: 回调地狱. 导致后期阅读和维护代码特别麻烦. 所以 es6 的 Promise 就是为了解决这个麻烦而出来的新对象, 之前早就存在, ES6 将其写进了语言标准, 统一了用法, 原生提供了 Promise 对象.
定义
Promise 对象是为了简化异步编程. 解决回调地狱情况.
Promise, 简单说就是一个容器, 里面保存着某个未来才会结束的事件 (通常是一个异步操作) 的结果. 重点是取决与这个事件之后的一系列动作, then()或 catch()的等等.
从语法上说, Promise 是一个对象, 从它可以获取异步操作的消息. Promise 对象用于延迟(deferred) 计算和异步(asynchronous ) 计算. 一个 Promise 对象代表着一个还未完成, 但预期将来会完成的操作. 这样表示了一旦用了 promise 对象, 就不能退出, 直到出现结果为止(resloved 或 rejected)
Promise 是一个对象, 可以用构造函数来创建一个 Promise 实例.
- let promise = new Promise((resolve, reject) =>{
- // .... some coding
- if (true){ // 异步操作成功
- resolve(value);
- } else {
- reject(error);
- }
- })
- promise.then(value=>{
- // 成功的回调函数
- }, error=>{
- // 失败后的回调函数
- })
- console.log(typeof promise) // object
参数解释
params: 传参是一个回调函数. 这个回调函数有两个参数 resolve 和 reject.
resolve: 将 Promise 对象的状态从 "未完成" 变为 "成功"(即从 pending 变为 resolved), 在异步操作成功时调用, 并将异步操作的结果, 作为参数传递出去.(简单来说就是成功了的执行)
reject: 将 Promise 对象的状态从 "未完成" 变为 "失败"(即从 pending 变为 rejected), 在异步操作失败时调用, 并将异步操作报出的错误, 作为参数传递出去.(简单来说就是失败了的执行)
promise 之后 then 的参数:
第一个参数是成功的回调函数, 必选
第二个参数是失败的回调函数, 可选
- // 成功时
- let promise = new Promise((resolve, reject) =>{
- console.log('开始')
- if (2> 1){ // 异步操作成功
- resolve({name:'peter',age:25});
- } else {
- reject(error);
- }
- })
- promise.then(value=>{
- // 成功的回调函数
- console.log(value)
- }, error=>{
- // 失败后的回调函数
- console.log(error)
- })
- // 开始
- // {name: "peter", age: 25}
- // 失败时
- let promise = new Promise((resolve, reject) =>{
- console.log('开始')
- if (2> 3){ // 异步操作成功
- resolve(a);
- } else {
- reject('未知错误');
- }
- })
- promise.then(value=>{
- // 成功的回调函数
- console.log(value)
- }, error=>{
- // 失败后的回调函数
- console.log(error)
- })
- // 开始
- // 未知错误
ps:Promise 实例化一个对象后,会立即实行.
- new Promise((resolve, reject)=>console.log('promise'));
- console.log('123');
- // promise
- // 123
这个结果发现, 先执行 promise 后执行 123.
Promise 的特点
对象的状态不受外界影响. Promise 对象代表一个异步操作, 有三种状态: pending(进行中),fulfilled(已成功)和 rejected(已失败). 只有异步操作的结果, 可以决定当前是哪一种状态, 任何其他操作都无法改变这个状态.
一旦状态改变, 就不会再变, 任何时候都可以得到这个结果. 就是成功了就一直是成功的状态 fulfilled, 失败一直是失败的状态 rejected.
如果改变已经发生了, 你再对 Promise 对象添加回调函数, 也会立即得到这个结果. 这与事件 (Event) 完全不同, 事件的特点是, 如果你错过了它, 再去监听, 是得不到结果的
promise 先按顺序实行完 promise 实例中方法再实行 then 中的 resolve 或者 reject.
- let promise = new Promise((resolve, reject)=>{
- console.log('promise')
- if (2> 1){ // 异步操作成功
- resolve({name:'peter',age:25});
- } else {
- reject(error);
- }
- console.log('end')
- })
- promise.then(
- value=>{
- console.log(value)
- },
- error=>{
- console.log(error)
- }
- )
- // promise
- // end
- // {name: "peter", age: 25}
Ajax 是最常见的异步操作方式, 那么用 promise 封装 Ajax 的例子
- const getJSON = function (url) {
- const promise = new Promise(function (resolve, reject) {
- const 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("xxxxx").then(function (value) {
- console.log('Contents:' + value);
- }, function (error) {
- console.error('出错了', error);
- });
Promise 方法
promise.then()
then() 为 Promise 实例添加状态改变时的回调函数, 上面已经提起过.
params 解释:
第一个参数是 resolved 状态的回调函数, 必选
第二个参数是 rejected 状态的回调函数, 可选
通常情况下, then 方法作为成功时的回调方法, catch方法作为失败时回调方法. catch()在后面, 可以理解为 then 方法中的 reject 参数
- let promise = new Promise((resolve, rejected)=>{
- if(2<3){
- resolve()
- }else{
- rejected()
- }
- })
- promise.then(resolve=>{
- console.log('right')
- }).catch(reject=>{
- console.log('error')
- })
ps: then 方法返回的是一个新的 Promise 实例(注意, 不是原来那个 Promise 实例).
- var aPromise = new Promise(function (resolve) {
- resolve(100);
- });
- var thenPromise = aPromise.then(function (value) {
- console.log(value);
- });
- var catchPromise = thenPromise.catch(function (error) {
- console.error(error);
- });
- console.log(aPromise !== thenPromise); // => true
- console.log(thenPromise !== catchPromise);// => true
所以每一个then()方法就是一个新 promise 对象. 因此可以采用链式写法, 即 then 方法后面再调用另一个 then 方法. 这样必须要传一个参数过去.
promise 的链式编程, 就是第一个的 Promise 实例的返回的值作为下一个 Promise 实例的参数.
- function start() {
- return new Promise((resolve, reject) => {
- resolve('start');
- });
- }
- start()
- .then(data => {
- // promise start
- console.log(data);
- return Promise.resolve(1); // 1
- })
- .then(data => {
- // promise 1
- console.log(data);
- })
- // start
- // 1
从上面例子可知:
start 函数里 resolve 里传了一个参数'start'
第一个 then 方法接受了 start, 然后 return 一个成功的值 1
第二个 then 方法接受上一个 then 传来的值 1
Promise.catch()
catch()和 then()都是挂载在 promise 对象的原型上的.
Promise.prototype.catch 方法是 promise.then(null, rejection)或 promise.then(undefined, rejection)的别名, 用于指定发生错误时的回调函数.
一般是等价于:(在遇到失败的情况下)
Promise.catch() <=> promise.then(null,e=>reject())
如果异步操作抛出错误, 状态就会变为 rejected, 就会调用 catch 方法指定的回调函数, 处理这个错误. 另外, then 方法指定的回调函数, 如果运行中抛出错误, 也会被 catch 方法捕获.
- const promise = new Promise(function(resolve, reject) {
- throw new Error('test');
- });
- promise.catch(function(error) {
- console.log(error);
- });
- // Error: test
Promise 对象的错误具有 "冒泡" 性质, 会一直向后传递, 直到被捕获为止. 也就是说, 错误总是会被下一个 catch 语句捕获.
- function throwError(value) {
- // 抛出异常
- throw new Error(value);
- }
- // <1> onRejected 不会被调用
- function badMain(onRejected) {
- return Promise.resolve(42).then(throwError, onRejected);
- }
- // <2> 有异常发生时 onRejected 会被调用
- function goodMain(onRejected) {
- return Promise.resolve(42).then(throwError).catch(onRejected);
- }
- // 运行示例
- badMain(function(){
- console.log("BAD");
- });
- goodMain(function(){
- console.log("GOOD");
- });
- // GOOD
在上面的代码中, badMain 是一个不太好的实现方式(但也不是说它有多坏), goodMain 则是一个能非常好的进行错误处理的版本.
为什么说 badMain 不好呢?, 因为虽然我们在 .then 的第二个参数中指定了用来错误处理的函数, 但实际上它却不能捕获第一个参数 onFulfilled 指定的函数 (本例为 throwError ) 里面出现的错误. 也就是说, 这时候即使 throwError 抛出了异常, onRejected 指定的函数也不会被调用(即不会输出 "BAD" 字样).
与此相对的是, goodMain 的代码则遵循了 throwError→onRejected 的调用流程. 这时候 throwError 中出现异常的话, 在会被方法链中的下一个方法, 即 .catch 所捕获, 进行相应的错误处理.
.then 方法中的 onRejected 参数所指定的回调函数, 实际上针对的是其 promise 对象或者之前的 promise 对象, 而不是针对 .then 方法里面指定的第一个参数, 即 onFulfilled 所指向的对象, 这也是 then 和 catch 表现不同的原因.(详见 JavaScript Promise 迷你版)
这个是从别人的博客拿来的代码和解释,了那么多, 总结为, catch 能够捕获它之前的异常, 而在 then()方法中第二个参数是没办法捕获到的, 因为实行了 resolve 方法.
Promise.resolve()
看字面量的意思, 是返回一个成功的 promise 实例.
Promise.resolve() <=> new Promise((resolve,rejected)=>resolve())
最常见的就是将不是 promise 对象的异步操作转化为 promise 对象.
该方法有四个参数:
无参数
直接返回一个 resolved 状态的 Promise 对象, 所谓的字面量意思.
参数是一个 Promise 实例
Promise.resolve 将不做任何修改, 原封不动地返回这个实例.
参数是一个 thenable 对象
所谓的 thenable 对象指的就是具有 then方法的对象, 类似于类数组具有数组的 length, 但不是数组一样.
JavaScript let thenable = { then: function(resolve, reject) { resolve(42); } }; var promise = Promise.resolve(thenable) .then(value=>console.log(value));// 42
上面的例子就是将具有 then 方法的 thenable 对象转化为promise 对象, 并且立即执行 resolve 方法.
参数不是具有 then 方法的对象, 或根本就不是对象
如果参数是一个原始值, 或者是一个不具有 then 方法的对象, 则 Promise.resolve 方法返回一个新的 Promise 对象, 状态为 resolved.
JavaScript var str = '17 号' Promise.resolve(str).then(value=>console.log(value)) // 17 号
Promise.reject()
返回一个新的 Promise 实例, 该实例的状态为 rejected. 用法和 resolve 一样, 但是都是以失败返回结果
Promise.reject() <=> new Promise((resolve,reject) =>reject())
ps:Promise.reject()方法的参数, 会原封不动地作为 reject 的理由, 变成后续方法的参数. 这一点与 Promise.resolve 方法不一致.
- const thenable = {
- then(resolve, reject) {
- reject('出错了');
- }
- };
- Promise.reject(thenable)
- .catch( e=> {
- console.log(e)
- })
- // 返回的是thenable 对象
- Promise.all()
Promise.all 接收一个 promise 对象的数组作为参数, 当这个数组里的所有 promise 对象全部变为 resolve 或 reject 状态的时候, 它才会去调用 .then() 方法.
该方法的参数是一个数组
该方法的参数数组是必须含有promise 对象的数组
只有数组中所有的 promise 对象都变成 resolve 或者 reject 才能进行下一步操作.
- // `delay` 毫秒后执行 resolve
- function timerPromisefy(delay) {
- return new Promise(function (resolve) {
- setTimeout(function () {
- resolve(delay);
- }, delay);
- });
- }
- var startDate = Date.now();
- // 所有 promise 变为 resolve 后程序退出
- Promise.all([
- timerPromisefy(1),
- timerPromisefy(32),
- timerPromisefy(64),
- timerPromisefy(128)
- ]).then(function (values) {
- console.log(Date.now() - startDate + 'ms');
- console.log(values);
- });
- // 129ms
- // 1,32,64,128
从上述结果可以看出, 传递给 Promise.all 的 promise 并不是一个个的顺序执行的, 而是同时开始, 并行执行的.
假设法: 如果这些 promise 全部串行处理的话, 那么需要 等待 1ms → 等待 32ms → 等待 64ms → 等待 128ms , 全部执行完毕需要 225ms 的时间. 但实际上不是, 而是 129ms 左右.
Promise.race()
和 Promise.all()方法一样,参数是一个数组, 但是只要有一个 promise 对象更改状态时就实行下一步.
- // `delay` 毫秒后执行 resolve
- function timerPromisefy(delay) {
- return new Promise(function (resolve) {
- setTimeout(function () {
- resolve(delay);
- }, delay);
- });
- }
- // 任何一个 promise 变为 resolve 或 reject 的话程序就停止运行
- Promise.race([
- timerPromisefy(1),
- timerPromisefy(32),
- timerPromisefy(64),
- timerPromisefy(128)
- ]).then(function (value) {
- console.log(value); // => 1
- });
上面的例子是 1 秒后就 resolve 了, 所以直接 then()了.
Promsie.finally()
该方法用于指定不管 Promise 对象最后状态如何, 都会执行的操作. 无论 resolve 还是 reject 都会实行的操作, 不依赖其他的操作. 按照执行顺序.
- function promise(){
- return new Promise((resolve, reject) => {
- resolve('success');
- })
- };
- promise().then(data => {
- console.log(data)
- return Promise.reject('fail')
- }).finally(() => {
- console.log('end')
- }).catch(data =>{
- console.log(data)
- })
- // success
- // end
- // fail
从上面的例子可知, 是按照 promise 的实行顺序执行的, 在 then()中, 要求返回一个失败的状态, 但是却没先实行失败的方法, 而是按照顺序实行了 finally 方法.
Promise.done()
Promise 对象的回调链, 不管以 then 方法或 catch 方法结尾, 要是最后一个方法抛出错误, 都有可能无法捕捉到(因为 Promise 内部的错误不会冒泡到全局). 因此, 我们可以提供一个 done 方法, 总是处于回调链的尾端, 保证抛出任何可能出现的错误.
- Promise.prototype.done = function (resolve, reject) {
- this.then(resolve, reject)
- .catch( function (reason) {
- // 抛出一个全局错误
- setTimeout( () => { throw reason }, 0);
- });
- }
- // 使用示例
- var p = new Promise( (resolve, reject) => {
- resolve('p');
- });
- p.then(ret => {
- console.log(ret);
- return 'then1';
- }).catch( err => {
- console.log(err.toString());
- }).then( ret => {
- console.log(ret);
- return 'then2';
- }).then( ret => {
- console.log(ret);
- x + 2;
- }).done();
该例子参考别人的例子. 发现到最后直接抛出了 'Uncaught ReferenceError: x is not defined'. 说明最后一个 then 实行时会抛出异常, 也可以类似于 catch 方法吧.
总结
总结来说 Promise 其实就是做了一件事情, 那就是对异步操作进行了封装, 然后可以将异步操作以同步的流程表达出来, 避免了层层嵌套的回调地狱, 提供统一的接口方法, 使得控制异步操作更加容易, 但是也有一定的缺点, promise 一旦没确定状态, 是没法终止的, 同样的, 也无法取消 promise.
如果本文有什么不对的地方, 欢迎指出, 谢谢, 大家一起进步加油. 我把笔记放到了, 如果满意的话给个 star.
参考资料
ES6 标准入门 http://es6.ruanyifeng.com/#docs/promise
JavaScript Promise 迷你版 http://liubin.org/promises-book/#introduction
学习 Promise https://segmentfault.com/a/1190000007685095
来源: https://www.cnblogs.com/sqh17/p/10480431.html