八. ES6的Promise详解

1. Promise理解

ES6中一个很是重要和好用的特性就是Promise类。javascript

可是初次接触Promise会一脸懵逼,这TM是什么东西?看看官方或者一些文章对它的介绍和用法也是一头雾水。java

Promise究竟是作什么的呢? => Promise是异步编程的一种解决方案。通常状况下有异步操做时,使用Promise对这个异步操做进行封装。ajax

那何时咱们会来处理异步事件呢?一种很常见的场景应该就是 网络请求 了。编程

咱们封装一个网络请求的函数不能当即拿到结果,因此不能像简单的 3+4=7 同样将结果返回。因此咱们每每会传入另一个函数,在数据请求成功时将数据经过传入的函数回调出去。若是只是一个简单的网络请求,那么这种方案不会给咱们带来很大的麻烦。可是当网络请求很是复杂时就会出现回调地狱。promise

OK,我以一个很是夸张的案例来讲明。咱们来考虑下面的场景(有夸张的成分):服务器

  • 咱们须要经过一个url1从服务器加载一个数据data1,data1中包含了下一个请求的url2
  • 咱们须要经过data1取出url2,从服务器加载数据data2,data2中包含了下一个请求的url3
  • 咱们须要经过data2取出url3,从服务器加载数据data3,data3中包含了下一个请求的url4
  • 发送网络请求url4,获取最终的数据data4
$.ajax('url1',function(data1) {
   $.ajax(data1['url2'],function(data2) {
     $.ajax(data2['url3'],function(data3) {
       $.ajax(data3['url4'],function(data4) {
         console.log(data4);
       })
     })
   })
})

上面的代码有什么问题吗?正常状况下不会有什么问题,能够正常运行而且获取咱们想要的结果。可是这样额代码难看并且不容易维护,咱们更加指望的是一种更加优雅的方式来进行这种异步操做。如何作呢?就是使用Promise,Promise能够以一种很是优雅的方式来解决这个问题。网络

2. Promise基本使用

2.1 定时器的异步事件案例

咱们先来看看Promise最基本的语法。异步

这里咱们用一个定时器来模拟异步事件:假设下面的data是从网络上1秒后请求的数据,console.log就是咱们的处理方式。异步编程

setTimeout(() => {
	console.log('Hello World');
},1000)

上面是咱们过去的处理方式,咱们将它用Promise进行封装(虽然这个例子会让咱们感受脱裤放屁画蛇添足)函数

  • 首先下面的Promise代码明显比上面的代码看起来还要复杂。
  • 其次下面的Promise代码中包含的resolve、reject、then、catch都是些什么东西?
  • 咱们先无论复杂度的问题,由于这样的一个屁大点的程序根本看不出来Promise真正的做用。
new Promise((resolve,reject) => {
   setTimeout(() => {
     resolve('Hello World')
     //reject('Error Data')
   }, 1000)
 }).then(data => {
   console.log(data) //Hello World
 }).catch(error => {
   console.log(error) //Error Data
})
//注意:另外一种写法,成功和失败的消息均可以写在then这个回调函数中
new Promise((resolve,reject) => {
   setTimeout(() => {
     resolve('Hello World')
     //reject('Error Data')
   }, 1000)
 }).then(data => {
   console.log(data) //Hello World
 },error => {
   console.log(error) //Error Data
})

咱们先来认认真真的读一读这个程序到底作了什么?

  • new Promise很明显是建立一个Promise对象

  • 小括号中(resolve, reject) => {}也很明显就是一个函数,并且咱们这里用的是箭头函数

    • 可是resolve, reject它们是什么呢?

    • 咱们先知道一个事实:在建立Promise时传入的这个箭头函数是固定的(通常咱们都会这样写)

    • resolve 和 reject 它们两个也是函数,一般状况下咱们会根据请求数据的成功和失败来决定调用哪个。

  • 成功仍是失败?

    • 若是是成功的,那么一般咱们会调用resolve(messsage),这个时候咱们后续的then会被回调
    • 若是是失败的,那么一般咱们会调用reject(error),这个时候咱们后续的catch会被回调

OK,这就是Promise最基本的使用了。

2.2 Promise三种状态

首先, 当咱们开发中有异步操做时, 就能够给异步操做包装一个Promise

异步操做以后会有三种状态

  • pending:等待状态,好比正在进行网络请求,或者定时器没有到时间。
  • fulfill:知足状态,当咱们主动回调了resolve时,就处于该状态而且会回调 then()
  • reject:拒绝状态,当咱们主动回调了reject时,就处于该状态而且会回调 catch()
new Promise((resolve,reject) => {
   setTimeout(() => {
     //resolve('Hello World')
     reject('Error Data')
   }, 1000)
 }).then(data => {
   console.log(data)
 }).catch(error => {
   console.log(error)
})

3. Promise链式调用

3.1 使用方式一:每一次调用都是异步操做
new Promise((resolve, reject) => {
  //1.第一次模拟网络请求的代码
  setTimeout(() => {
    resolve("Hello World");
  }, 2000);
}).then((data) => {
  //第一次拿到结果的处理代码
  console.log(data); //Hello World
  return new Promise((resolve, reject) => {
    //第二次模拟网络请求的代码
    setTimeout(() => {
       resolve(data + " 111");
    }, 2000);
  }).then((data) => {
    //第二次拿到结果的处理代码
    console.log(data); //Hello World 111
    return new Promise((resolve, reject) => {
       //第三次模拟网络请求的代码
       setTimeout(() => {
         resolve(data + "222");
       }, 2000);
    }).then((data) => {
       //第三次拿到结果的处理代码
       console.log(data); //Hello World 111222
       return new Promise((resolve, reject) => {
         //第四次模拟网络请求错误的代码
         setTimeout(() => {
           reject(data + "error");
         }, 2000);
       }).then((data) => {
          //这里没有输出,这部分代码不会执行
          console.log(data);
          return new Promise((resolve, reject) => {
            setTimeout(() => {
               resolve(data + "333");
            }, 2000);
          });
       }).catch((data) => {
          //第四次拿到结果的处理代码
          console.log(data); //Hello World 111222error
          //第五次模拟网络请求的代码
          return new Promise((resolve, reject) => {
            setTimeout(() => {
               resolve(data + "444");
            }, 2000);
          }).then((data) => {
             //第五次拿到结果的处理代码
             console.log(data); //Hello World 111222error444
             //..不能再套娃了
          });
        });
    });
  });
});
//注意:其实reject是可选的,当咱们不用的时候能够只写 resolve => {}
3.2 使用方式二:只有第一次调用是异步操做

只有第一次调用是异步操做,后面的调用不是异步操做可是咱们但愿后面的调用也是分层的

new Promise((resolve,reject) => {
  setTimeout(() => {
     resolve('Hello World')
  }, 1000)
}).then(data => {
  console.log(data) //Hello World
  return Promise.resolve(data + ' 111')
}).then(data => {
  console.log(data) //Hello World 111
  return Promise.resolve(data + '222')
}).then(data => {
  console.log(data) //Hello World 111222
  return Promise.reject(data + 'error')
}).then(data => {
  console.log(data) 
  return Promise.resolve(data + '333')
}).catch(data => {
  console.log(data) //Hello World 111222error
  return Promise.resolve(data + ' 444')
}).then(data => {
  console.log(data) //Hello World 111222error444
})

这里咱们直接经过Promise包装了一下新的数据,将Promise对象返回了

  • Promise.resovle():将数据包装成Promise对象,而且在内部回调resolve()函数

  • Promise.reject():将数据包装成Promise对象,而且在内部回调reject()函数

链式调用简写

简化版代码:若是咱们但愿数据直接包装成Promise.resolve,那么在then中能够直接返回数据
注意下面的代码中我将return Promise.resovle(data)改为了return data结果依然是同样的

new Promise((resolve,reject) => {
  setTimeout(() => {
     resolve('Hello World')
  }, 1000)
}).then(data => {
  console.log(data) //Hello World
  return data + ' 111'
}).then(data => {
  console.log(data) //Hello World 111
  return data + '222'
}).then(data => {
  console.log(data) //Hello World 111222
  return Promise.reject(data + 'error')
}).then(data => {
  console.log(data) 
  return data + '333'
}).catch(data => {
  console.log(data) //Hello World 111222error
  return data + ' 444'
}).then(data => {
  console.log(data) //Hello World 111222error444
})

4. Promise的all方法使用

4.1 案例

假设有两个网络请求,咱们必需要保证两个网络请求都成功后才能执行一些操做。即两个网络请求加上后续的操做才是一个完整的业务。怎么实现呢?

之前的实现方式

//两个flag
let isResult1 = false;
let isResult2 = false;
//第一个请求
$.ajax({
    url:'url1'
    success: () => { 
    	console.log("结果一");
    	isResult1 = true
    	handleResult()
	}
})
//第二个请求
$.ajax({
    url:'url2'
    success: () => { 
    	console.log("结果二");
    	isResult2 = true
    	handleResult()
	}
})

function handleResult() {
    if(isResult1 && isResult2) {
        //后续操做
    }
}
4.2 all方法的使用
Promise.all([
    new Promise((resolve,reject) => {
        //模拟网络请求一
        setTimeout(() => {
            resolve('result1');
        },1000)
    }),
    new Promise((resolve,reject) => {
        //模拟网络请求二
        setTimeout(() => {
            resolve('result2');
        },5000)
    }),
]).then(results => {
    //5秒后才会打印
    console.log(results[0]); //结果一
    console.log(results[1]); //结果二
})
相关文章
相关标签/搜索