天天看点

ES6语法之promise

    promise是ES6推出的一个新的对象,专门为了解决回调地狱的问题的。

    回调函数:作为函数参数的函数,在函数执行完毕之后再执行,有同步回调和异步回调两种,同步回调:主函数等待回调函数执行完毕之后再执行,异步回调:主函数无需等到回调函数完成可继续执行,此时的回调函数被放置任务队列中。

    我们都知道JS是单线程运行的,当遇到异步的任务时,它会将异步任务放置到队列中去,不会执行异步任务,异步任务只有到主程序执行完之后再去执行。举个常见的例子:

for(var i=0;i<5;i++){
  setTimeOut(function(){
  console.log(i)
},1000)
}
           

    结果输入5个5,这时因为setTimeOut是一个异步操作,只有等主程序的for循环完毕之后才会输出,因此输出的i为5个5。

   正是因为JS是一个单线程,所以JS推出了异步操作来解决JS在某些情况下的不适用 ,如循环等死的情况。

   问题来了,既然主程序在执行完毕之后就会执行任务队列,异步操作都会被放置在任务队列里,那么什么时候知道该异步操作完成了呢?看下面的例子:

const fs =require('fs');
const path =require('path');

function getFile(){
    fs.readFile(path.join(__dirname,'./file/1.txt'),'utf-8',(err,dataStr)=>{  //异步操作
        if(err) throw err
         console.log('异步操作发生')
      return dataStr    //返回1.txt的内容
      });
};
var file1=getFile();
console.log(file1);   //undefined
           

        这里为什么会出现undefined呢?按照道理来说file1的值应该是1.txt的内容,但是,我们说了异步操作会放到任务队列里,所以主程序在执行异步操作语句时,会将此异步操作放到任务队列中,转而执行完主程序再执行任务队列,所以getFile中没有一个return,返回的值是undefined。

     如何解决这个问题呢?我们可以创建一个异步回调函数,回调函数的特点是,作为函数参数的函数,能处理异步操作返回后的结果,当异步操作完成之后执行回调函数,主程序执行完之后,执行回调函数。

const fs =require('fs');
const path =require('path');

function getFile(unpath,callback){
    fs.readFile(unpath,'utf-8',(err,dataStr)=>{  //异步操作
      //当有错误时,就不执行后续的代码了 
      if(err)  return callback(err.message)
        
        return callback(dataStr)    //返回1.txt的内容
      });
};
  getFile(path.join(__dirname,'./file/1.txt'),(dataStr)=>{  //这是一个回调函数
    console.log(dataStr);  //1.txt的内容
});

           

    getFile中的第二个参数是一个回调函数,当异步操作有了结果之后,再执行回调函数(回调回调,回头调用的意思),但是此时的回调函数还没有被主函数执行到,仍然是在任务队列中,只有主程序执行完了才会去执行回调函数。不信我们看下面的例子

function b(){
        setTimeout(function(){  //模拟异步操作
            console.log("回调函数")},1000)
      }
  function a(callback){
       callback();
      console.log('主函数执行完毕');
  }
  a(b);
  //输出:
//   主函数执行完毕
// 回调函数  
           

相关的回调函数的解释:https://www.cnblogs.com/moxiaowohuwei/p/8438236.html

  回调地狱:指在回调函数中嵌套回调函数,如此嵌套多层回调函数导致,代码不够整洁,容易出现错误等

   看下面的例子:

const fs =require('fs');
const path =require('path');


//需求:读取1.txt后再读取2.txt再读取3.txt
function getFile(unpath,succd,errcd){
    fs.readFile(unpath,'utf-8',(err,dataStr)=>{  //异步操作
      //当有错误时,就不执行后续的代码了 
      if(err)  return errcd(err.message)
        
        return succd(dataStr)    //返回内容
      });
};
  getFile(path.join(__dirname,'./file/1.txt'),function(data){
       console.log('成功了:'+data);  //1.txt的内容
          getFile(path.join(__dirname,'./file/2.txt'),function(data){
            console.log('成功了:'+data);  //2.txt的内容
             getFile(path.join(__dirname,'./file/3.txt'),function(data){
               console.log('成功了:'+data);  //3.txt的内容
       
              },function(err){
            console.log('失败了:'+err)    //1.txt的错误处理
          });
    },function(err){
      console.log('失败了:'+err)     //2.txt的错误处理
      });
 },function(err){
  console.log('失败了:'+err)  //3.txt的错误处理
 });

           

这里的先读取1再读取2再读取3,看着不难理解,但是如果是要读取10000个文件呢?那时候代码的混乱程度绝不一般。这就是回调地狱:在回调中调用回调,回调的回调再调用回调。。。。

promise的出现正式解决了回调地狱的问题,Promise代表的是一个异步操作对象

Promise创建之后会立即执行,所以要将promise对象封装在一个函数中,当函数被调用时才执行promise的构造函数

  var promise=new Promise(function(){
       fs.readFile(path.join(__dirname,'./file/1.txt'),'utf-8',function(err,data){
           if(err) return err
          console.log(data);     //输出1.txt内容
        })
 });
//立即输出1.txt,哪怕我们没有调用
//使用一个函数将promise对象封装起来,当函数被调用时,才被调用
function getFile(){
  var promise=new Promise(function(){
       fs.readFile(path.join(__dirname,'./file/1.txt'),'utf-8',function(err,data){
           if(err) return err
          console.log(data);     //输出1.txt内容
        })
 });
}
           

我们要使用Promise的话,要用到Promise对象的方法,那么就需要在封装Promise的函数中将Promise return出来:

function getFile(){
    return new Promise(function(){
       fs.readFile(path.join(__dirname,'./file/1.txt'),'utf-8',function(err,data){
         if(err) return err
         console.log(data)
      })
     })
}
           

在控制台输入:console.dir(Promise):

ES6语法之promise

可以看到Promise有各种方法,比较重要的有:.then(编写回调函数)、.catach(捕获异常)、resolve(成功回调)、reject(失败回调)

我们要在Promise的构造函数中,使用resolve执行成功的回调函数,使用reject执行失败的回调函数。

function getFile(path){
    return new Promise(function(resolve,reject){  //resolec和reject是形参
       fs.readFile(path),'utf-8',function(err,data){
         if(err) reject(err)  //失败的操作,执行reject回调函数
         resolve(data)   //成功的操作
      })
     })
}
           

这样getFile是一个合格的promise对象了,在调用getFile时,我们还需要使用.then为异步操作指定resovle成功回调和reject失败回调函数,reject可以不写,但是resolve一定要写。

function getFile(path){
    return new Promise(function(){
       fs.readFile(path,'utf-8',function(err,data){
         if(err) reject(err)
         resolve(data)
      })
     })
}
getFile(path.join(__dirname,'./file/1.txt')).then(function(data){
      console.log(data);  //成功的回调,实现了resolve
     },function(err){
     console.log(err);   //失败的回调,实现了reject
  })
           

总结一下,创建Promise的步骤

  1. 要想正确的使用Promise,那么就要创建一个函数封装Promise对象,并return出去

  2. 在Promise的构造函数中,写入异步操作的语句,使用resolve()执行成功操作,reject()执行失败操作

  3. 封装Promise的函数.then(resolve,reject)编写成功和失败的回调函数,reject可以不写,一旦遇到失败就会停止执行,当你给每一个Promise写rejct的时候,那么就算遇到错误也可以继续执行

Promise解决回调地狱的问题:

不使用Promise:

const fs =require('fs');
const path =require('path');


//需求:读取1.txt后再读取2.txt再读取3.txt
function getFile(unpath,succd,errcd){
    fs.readFile(unpath,'utf-8',(err,dataStr)=>{  //异步操作
      //当有错误时,就不执行后续的代码了 
      if(err)  return errcd(err.message)
        
        return succd(dataStr)    //返回内容
      });
};
  getFile(path.join(__dirname,'./file/1.txt'),function(data){
       console.log('成功了:'+data);  //1.txt的内容
          getFile(path.join(__dirname,'./file/2.txt'),function(data){
            console.log('成功了:'+data);  //2.txt的内容
             getFile(path.join(__dirname,'./file/3.txt'),function(data){
               console.log('成功了:'+data);  //3.txt的内容
       
              },function(err){
            console.log('失败了:'+err)    //1.txt的错误处理
          });
    },function(err){
      console.log('失败了:'+err)     //2.txt的错误处理
      });
 },function(err){
  console.log('失败了:'+err)  //3.txt的错误处理
 });

           

    promise怎么解决回调地狱的呢?在.then中的resolve中return 下一次执行的回调,在此结果上再使用.then,当然了,要想再某一层抛出错误时继续执行,那么就再reject上也return下一次的的回调

//  需求:先读取1.txt再读取2.txt再读取3.txt
//  Promise对象带有resolve(成功回调) reject(失败回调)
//  Promise传入的构造函数
function getFile(path){
      //返回一个prommise对象,这个函数getFile就是一个Promise对象
    return new Promise(function(resolve,reject){    //这个Promise构造函数可以传入一个函数,这个函数有两个参数:resolve和reject
         fs.readFile(path,'utf-8',function(err,data){   //异步操作
           if(err) reject(err)   //reject输出错误信息
            resolve(data);   //resolve输出错误信息
        })
       })
  }
  //.then是resolve和reject具体的回调函数,,then(resolve,reject)第二个回调可以省略
  getFile(path.join(__dirname,'./file/1.txt')).then(function(data){  

    console.log(data)  //读取1.txt
    return getFile(path.join(__dirname,'./file/12.txt'))  //第一个回调中return第2个Promise

  }).then(function(data){   //这里的promise对象已经变成了第二个promise了

    console.log(data);   //读取2.txt
    return getFile(path.join(__dirname,'./file/3.txt'))   //第二个回调中return第3个Promise

  }).then(function(data){     //这里的promise对象已经变成了第二个promise了

     console.log(data);   //读取3.txt

  }).catch(function(err){   //捕获异常,一旦遇到异常就终止执行,转而处理异常
    console.log(err.message);
  })
  console.log("主函数执行")
//  输出:
//主函数执行
//1.txt内容
//2.txt内容
//3.txt内容
           

  getFile(path.join(__dirname,'./file/1.txt')).then---这是第一个Promise,根据传入的参数,作用是读取1.txt,接着,当读取1.txt成功之后,在resolve回调函数内部,我们return  getFile(path.join(__dirname,'./file/2.txt')),那么此时的 getFile(path.join(__dirname,'./file/1.txt')).then相当于 getFile(path.join(__dirname,'./file/2.txt')),也就是执行第一个回调函数的结果就是第二个Promise的对象,如此一来,就能解决回调地狱了。而且最后还可以使用.catach来捕获异常,当异常一发生时,终止promise,转而处理异常。

   如果我们希望异常出现时能够继续执行promise那么我们就可以为每一个promise指定一个reject处理异常

Promise执行过程:

  1. 先执行封装Promise的函数,当遇到异步操作时,将异步操作放入任务队列中,先执行主函数

  2. 主函数执行时遇到.then,定义了成功和失败的回调,继续执行主函数,等待异步操作完成

  3. 主函数执行完毕之后再去执行任务队列中的任务(此时异步操作完成了)

JQuery下Ajax的Promise实践

正常JQuery:

<!DOCTYPE html>
<html >
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <input type="button" value="获取数据" id="btn">

    <script src="./node_modules/jquery/dist/jquery.min.js"></script>
  
    <script>
      $(function () {
        $('#btn').on('click', function () {
          $.ajax({
            url: './data.json',  //请求地址
            type: 'get',    //请求类型
            dataType: 'json',  //请求文件类型
            success:function(data){   //请求成功的回调
              console.log(data);
            }
          })
        })
      });
    </script>

</body>
</html>
           

打开Vscode,按照图下方法运行:

ES6语法之promise

运行结果:

ES6语法之promise

JQuery+Promise:

<!DOCTYPE html>
<html >
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <input type="button" value="获取数据" id="btn">

    <script src="./node_modules/jquery/dist/jquery.min.js"></script>
  
    <script>
      $(function () {
        $('#btn').on('click', function () {
          $.ajax({   //这个ajax就是一个异步操作,我们将它当作是一个promise对象
            url: './data.json',
            type: 'get',
            dataType: 'json'
          }).then(function(data){  //resolve成功回调
              console.log(data)
          })
            
        })
      });
    </script>

</body>
</html>