ES6特性:Promise

Promise

1、简介

1.1 概述

Promise 是异步编程的一种新的解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。

常见的异步编程有fs文件操作,数据库操作,ajax数据交互,定时器

从功能上来说,promise就是一个容器,封装一个异步操作并可以获取其成功/失败的结果值。

从语法上说,Promise 是一个构造函数,从它可以获取异步操作的消息。

1.2 promise特点

(1)链式调用,解决了回调地狱问题:在传统的回调函数编程中,我们需要把回调函数作为参数传递给异步操作函数,这种方式有时会导致回调函数嵌套过深,代码结构难以理解和维护。在 Promise 中,then 方法可以链式调用每个 then 方法返回一个新的 Promise 对象,可以继续进行链式调用。这种方式不仅保证了代码的结构清晰可读,而且可以灵活处理异步操作之间的依赖关系。
(2)错误处理:在 Promise 中,通过 catch 方法可以捕获 Promise 对象的错误信息。这种方式避免了传统回调函数中错误处理困难的问题,使得代码更加规范和易于维护。
(3)多个异步操作的并行执行:在 Promise 中,通过 Promise.all 方法可以观察多个 Promise 对象的状态,并在它们全部完成后进行处理。这种方式可以使得多个异步操作并行执行,提高程序的运行效率。
(4)Promise 有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)。状态只能从 pending 转变为 fulfilled 或 rejected,并且一旦转变就不能再改变。

(1)回调地狱(俗称套娃)
回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件
(2)回调地狱带来的缺点
不便于阅读,不便于异常操作
(3)解决方案
promise链式调用

2、Promise的状态

Promise对象代表一个异步操作有三种状态:
pending(进行中)
fulfilled(已成功)
rejected(已失败)

状态发生改变之后就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)

3、Promise实例化

Promise构造函数:Promise(excutor){}
参数excutor是一个函数,也称为执行器,执行器中又传入两个参数resolve和reject,这两个参数同样也是函数,分别用于处理成功回调和失败回调。
executor执行器会在Promise内部立即同步调用,异步操作在执行器中执行。
当promise成功时,调用resolve函数,失败时,调用reject函数。这两个函数中均可以传参。

const promise = new Promise(function(resolve,reject){
	//...some code
	if(/*异步操作成功*/{
		resolve(value);
		// 状态由pending变为fulfilled
	}else{
		reject(error);
		// 状态由pending变为rejected
	}
})

例如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>测试</title>
</head>
<body>
    <script>
        let promise = new Promise(function (resolve, reject) {
            if (3 < 5) {
                resolve("是正确的");
            } else {
                reject("是错误的");
            }
        })
        console.log(promise);
    </script>
</body>
</html>

结果:
在这里插入图片描述

4、Promise的原型方法

定义在Promise.prototype中的方法,通过Promise实例可以直接调用。

4.1 Promise.prototype.then((onResolved,onRejected) => {})

当状态由pending变为fulfilled的时候执行该回调函数,
参数:
最多需要有两个参数,参数分别是Promise 的成功和失败情况的回调函数。
返回值:
返回一个新的Promise实例对象,因此可以使用链式调用

当一个 Promise 完成(fulfilled)或者失败(rejected)时,返回函数将被异步调用(由当前的线程循环来调度完成)。具体的返回值依据以下规则返回。如果 then 中的回调函数:

  1. 返回了一个值,那么 then 返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
  2. 没有返回任何值,那么 then 返回的 Promise 将会成为接受状态,并且该接受状态的回调函数的参数值为 undefined。
  3. throw抛出一个错误,那么 then 返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
  4. 返回一个已经是接受状态的 Promise,那么 then 返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
  5. 返回一个已经是拒绝状态的 Promise,那么 then 返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
  6. 返回一个未定状态(pending)的 Promise,那么 then 返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。

将上面的规则简单总结:
1、如果回调函数中的返回结果是promise对象,则对象状态由回调函数的执行结果决定
2、如果回到函数中的返回结果为非promise对象(无论是字符串、undefined…只要不是promise对象),对象状态均为成功,返回值为对象成功调用中的值。
3、throw抛出错误,状态为rejected

  • 案例一:
// 第一种方式:使用readFile读取文件,普通回调实现
let fs = require('fs')
fs.readFile('./resource/article.txt',(err,data) => {
    if(err){
        throw err;
    }else{
        console.log(data.toString());
    }
})

// 第二种方式:promise实现
let promise = new Promise((resolve,reject) => {
    fs.readFile('./resource/article.txt',(err,data) => {
        if(err){
            reject(err)
        }else{
            resolve(data)
        }
    })
})

promise.then(data=>{
    console.log(data.toString());
},err=>{
    console.log(err);
})
  • 案例二:promise处理ajax请求
// 原生ajax处理请求
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button onclick="sendMsg()">点击</button>
    <script>
        function sendMsg(){
            // 创建xmlHttprequest对象
            let xhr = new XMLHttpRequest()
            // 建立连接
            xhr.open('get','https://api.apiopen.top/getJoke')
            // 发送请求
            xhr.send()
            // 接收请求,做出响应
            xhr.onreadystatechange = function(){
                if(xhr.readyState === 4){
                    if(xhr.status >= 200 && xhr.status <300){
                        // 成功则获取响应体
                        console.log(xhr.response);
                    }else{
                        // 失败就返回状态码
                        console.log(xhr.status);
                    }
                }
            }
        }
    </script>
</body>
</html>

//====================================
// promise处理ajax请求
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <button onclick="sendMsg()">点击</button>
    <script>
        function sendMsg() {
            let promise = new Promise((resolve, reject) => {
                // 创建xmlHttprequest对象
                let xhr = new XMLHttpRequest()
                // 建立连接
                xhr.open('get', 'https://api.apiopen.top/getJoke')
                // 发送请求
                xhr.send()
                // 接收请求,做出响应
                xhr.onreadystatechange = function () {
                    if (xhr.readyState === 4) {
                        if (xhr.status >= 200 && xhr.status < 300) {
                            // 成功则获取响应体
                            // console.log(xhr.response);
                            resolve(xhr.response)
                        } else {
                            // 失败就返回状态码
                            // console.log(xhr.status);
                            reject(xhr.status)
                        }
                    }
                }
            })

            promise.then((value)=>{
                console.log(value);
            },(error)=>{
                console.log(error);
            })
        }
    </script>
</body>

</html>

then中的成功回调中的参数中的值是从promise中的resolve中传过来的,同样失败回调的参数是从reject中传过来的。

4.2 Promise.prototype.catch(onRejected)

当状态由pending变为rejected的时候执行该回调函数,
参数:
回调函数,回调函数的参数为reject函数传递过来的值
返回值:
返回一个新的Promise实例对象,因此可以使用链式调用。

// 抛出一个错误,大多数时候将调用catch方法
let p1 = new Promise(function(resolve, reject) {
	throw 'Uh-oh!';
});

p1.catch(function(e) {
	console.log(e); // "Uh-oh!"
});

推荐使用catch方法,不要在then方法中定义rejected状态的回调函数;这是因为使用catch还可以捕获在then方法执行中存在的错误。

 // bad
promise.then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise.then(function(data) { 
    // success
  })
  .catch(function(err) {
    // error
  })

4.3 Promise.prototype.finally()

finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。这避免了同样的语句需要在then()和catch()中各写一次的情况。
参数:
回调函数,不接收任何参数
返回值:
返回一个新的Promise实例对象

let p1 = new Promise(function(resolve, reject) {
	throw 'Uh-oh!';
});
p1.catch(function(e) {
	console.log(e); // "Uh-oh!"
}).finally(function() { 
	console.log('这段代码最终都会执行'); 
});
  • promise封装ajax请求
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <title>promise基本使用</title>
</head>
<body>
    <script>
        let promise = new Promise(function(resolve,reject){
            // ajax发送异步请求
            $.ajax({
                // 请求路径
                url:'http://47.100.84.201:8888/carousel/findAll',
                // 成功回调
                success(res){
                    console.log("成功回调",res);
                    
                    // 通过resolve将成功的回调传递出去
                    //resolve(res);
                },
                // 失败回调
                error(err){
                    console.log("失败回调",err);
                    
                    // 通过reject将失败的回调传递出去
                    //reject(err);
                }
            })
        })

        // 通过promise实例对象的实例方法对数据进行操作
        promise
        .then(res => console.log("接收到resolve传递过来的数据" + res))
        .catch(err => console.log("接收reject传递的数据" + err))
        .finally(()=>{
            console.log("无论成功还是失败都会调用!")
        })
    </script> 
</body>
</html>

分析:当在promise实例对象中ajax的两个回调函数中使用console.log("成功回调",res)console.log("失败回调",err);语句反映调用结果(成功或失败)时,浏览器控制台并不会执行then\catch\finally方法中的内容,因为此时then方法中并没有接收到来自ajax的res,catch方法有没有接收到来自ajax的err,所以并不会执行箭头函数中的语句。
在这里插入图片描述
当改为resolve(res);reject(err);时结果如下:
在这里插入图片描述

  • promise层级调用
    假设有三个文件first.txt,second.txt,third.txt,读取文件
    第一种方式:
    使用普通方式进行层级读取文件(不推荐),如下:
const fs = require("fs");
fs.readFile('../FILE/first.txt',(err,data1) => {
    fs.readFile('../FILE/second.txt',(err,data2)=>{
        fs.readFile('../FILE/second.txt',(err,data3)=>{
            let result = data1 + '\t\n' + data2 + '\t\n' + data3;
            console.log(result);
            //...
            //如果后面还有其他文件呢,会导致回调地狱,代码会横向变得很宽很长,并且这里data不能重名,需要不断的取名字
        });
    });
});

第二种方式:
使用promise实现,解决缩进问题

const fs = require("fs");
// 初始化promise:读取第一个文件,使用resolve函数传递出去读取到的数据,用Promise对象接收
const promise = new Promise((resolve,reject)=>{
    fs.readFile('../FILE/first.txt',(err,data)=>{
        resolve(data);
    })
})

// 执行回调函数
promise.then(value => {
    //先看能不能获取到value值
    // console.log(value); //输出的是buffer
    // console.log(value.toString()); //可以使用toString方法转化buffer为正常字符串
   
    // then方法的返回值是一个promise对象,所以这里直接使用return返回一个promise对象
    return new Promise((resolve,reject)=>{
        // promise中的主要操作也是读取文件内容
        fs.readFile('../FILE/second.txt',(err,data)=>{
            // 将读取到的数据传递出去,这里将读取到的数据放到了数组中,一起传了出去
            // value是初始化时读取文件first.txt的内容,data指的是当前读到的文件内容
            resolve([value,data]);
        })
    })
    //使用链式调用方式继续调用,读取下一个文件的内容
}).then(value=>{
    return new Promise((resolve,reject)=>{
        fs.readFile('../FILE/third.txt',(err,data)=>{
            // 将读取到的data通过push方法添加进数组中
            // 这里的value是前面传过来的数组
            value.push(data);
            resolve(value);
        })
    })
}).then(value=>{
    // 输出一一读取文件后的结果
    console.log(value.toString()); // 这是第一个文件,这是第二个文件,这是第三个文件
    // 文件间通过逗号分隔
})

虽然目前使用promise的代码量确实比较多,但却可以避免代码横向增多的问题,不会影响代码阅读


【!注意】Promise 链式调用只需要一个 catch 方法即可。这个 catch 方法可以放在链式调用的最后,用于处理前面任何一个 then
方法中发生的错误。
具体地说,在 Promise 的链式调用中,每个 then 方法都可以返回一个新的 Promise 对象,用于处理异步操作的返回结果。如果
then 方法中抛出了异常(或者返回了 reject 状态的 Promise 对象),则后续的 then 方法不会被执行,一直等到遇到第一个
catch 方法才会开始执行 catch 方法的回调函数。
因此,一般情况下,只需要在链式调用的最后指定一个 catch 方法即可,用于处理整个链式调用中发生的任何异常。
同时需要注意的是,catch方法也可以放在某个 then 方法的后面,表示针对该 then 方法发生的异常进行处理。这种情况下,需要保证后面没有其他 then方法,否则异常依然会被传递到后面的 then 方法中。

5、静态方法

定义在Promise中的方法,通过Promise可以直接调用。

5.1 Promise.all([p1,p2])

Promise.all用于将多个 Promise 实例,包装成一个新的 Promise 实例
参数:
数组,数组中的元素为Promise实例
返回值:
返回一个新的promise,只有所有的promise都成功时才成功,只要有一个失败就直接失败。并且这个返回值是所有promise结果组成的数组。

const promise1 = Promise.resolve(3); //该方法用于将现有对象转化为Promise实例
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
	setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
	console.log(values);
});
// expected output: Array [3, 42, "foo"]

5.2 Promise.race([p1,p2,…])

Promise.race用于将多个 Promise 实例,包装成一个新的 Promise 实例
参数:
数组,数组中的元素为Promise实例
返回值:
Promise实例
当p1,p2之中有一个实例率先改变状态,该实例的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给该实例的回调函数。(谁执行的快就返回谁)

const promise1 = new Promise((resolve, reject) => {
		setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve, reject) => {
	setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then((value) => {
	console.log(value);
	// Both resolve, but promise2 is faster
});
// expected output: "two"

5.3 Promise.any([p1,p2])

用于将多个 Promise 实例,包装成一个新的 Promise 实例
参数:
数组,数组中的元素为Promise实例
返回值:
Promise实例,只要p1,p2状态有一个变为fulfilled,该实例的状态立即就变为fulfilledp1,p2状态都变为rejected,该实例状态才为rejected

const pErr = new Promise((resolve, reject) => {
	reject("总是失败");
});

const pSlow = new Promise((resolve, reject) => {
	setTimeout(resolve, 500, "最终完成");
});

const pFast = new Promise((resolve, reject) => {
	setTimeout(resolve, 100, "很快完成");
});

Promise.any([pErr, pSlow, pFast]).then((value) => {
	console.log(value);
	// pFast fulfils first
})
// expected output: "很快完成"

5.4 Promise.resolve()

用于将现有对象转化为Promise实例。
如果传入的参数为非promise类型的对象,则返回的结果为成功promise对象
如果传入的参数为promise类型的对象,则参数的结果决定了resolve的结果

参数:
任意值

const promise1 = Promise.resolve(123);
promise1.then((value) => {
	console.log(value);
	// expected output: 123
});

5.5 Promise.reject()

返回一个失败的promise对象。(返回的结果永远是失败的)
参数:
任意值,通常传入错误信息

Promise.reject(new Error('fail')).then(function() {
	// not called
}, function(error) {
	console.log(error); // Stacktrace
});

6、promise封装

  • promise封装fs读取文件操作
let fs = require('fs')

// 使用promise封装fs
function myReadFile(path){
    // 该方法返回一个promise对象
    return new Promise((resolve,reject) => {
        // 读取文件
        fs.readFile(path,(err,data) => {
            if(err) {
                reject(err);
            }else {
                resolve(data)
            }
        })
    })
}

// 调用封装的函数
myReadFile('./resource/article.txt').then(value => {
    console.log(value.toString());
},error => {
    console.log(error);
})

  • promise封装原生ajax
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        /* 
            封装一个函数sendAjAX请求 发送get AJAX请求
            参数:URL
            返回结果:Promise对象    
        */
       function sendAJAX(url){
           return new Promise((resolve,reject) => {
               const xhr = new XMLHttpRequest()
               xhr.open('get',url)
               xhr.send()
               xhr.onreadystatechange = function(){
                   if(xhr.readyState === 4){
                       if(xhr.status >= 200 && xhr.status < 300){
                           resolve(xhr.response)
                       }else {
                           reject(xhr.status)
                       }
                   }
               }
           })
       }

       // 调用封装函数
       sendAJAX('https://api.apiopen.top/getJock')
       .then(value => {
            console.log(value);
       },error => {
            console.log(error);
       })
    </script>
</body>
</html>
  • promise封装jquery-ajax
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <title>Document</title>
</head>

<body>
    <script>
        //封装ajax对象
        function myPromise(url, method, data) {
            //返回一个promise对象
            return new Promise(function (resolve, reject) {
                //ajax封装
                $.ajax({
                    url,
                    method,
                    data,
                    success(res){
                        resolve(res);
                    },
                    error(err){
                        reject(err); 
                    }
                })
            })
        }

        // 获取数据 
        // 使用链式调用myPromise().then().catch()
        let swiperData = myPromise("某个url地址",get);

        swiperData
        .then(res=>console.log(res,"promise.then的返回"))
        .catch(err=>console.log(err,"promise.catch的返回"));
    </script>
</body>

</html>
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>promise封装</title>
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
  <script>
    var baseUrl = "http://47.106.244.1:8099"
    // 封装工厂函数
    function getPromise(url, method = "get", data = null) {
      return new Promise((resolve, reject) => {
        $.ajax({
          url,
          method,
          data,
          success(res) {
            resolve(res);
          },
          error(err) {
            reject(err);
          }
        })
      });
    }
    let p1 = getPromise(baseUrl+'/manager/category/findAllCategory');
    let p2 = getPromise(baseUrl+'/manager/user/findAllUser');

    /* p1.then((res) => {
      console.log(res, '---');
    })
    p2.then((res) => {
      console.log(res, '+++')
    }) */
    // all  都成功就成功,有一个失败就失败
    // race 谁快用谁的结果
    // any 有成功就成功的,如果都失败就失败
    let p = Promise.any([p1, p2]);
    p.then((res) => {
      console.log(res, '++++');
    }).catch((err) => {
      console.log(err, '----');
    })
  </script>
</head>
<body>
</body>
</html>

7、其他实用的方法

7.1 util.promisify()方法

作用:可以传入一个遵循常见错误优先的回调风格的函数(即以(err,value)=> … 回调作为最后一个参数),并返回一个返回promise的版本。

形容词太多,简单来说就是将(err,value)=>... 风格的函数转换为promise风格的函数。

// util.promisify()方法
 const util = require('util')
 const fs = require('fs')

 // 返回一个新的函数
 let myreadFile = util.promisify(fs.readFile());

 myreadFile('./resource/article.txt').then(value => {
     console.log(value.toString());
 })

8、Promise的工作流程

在这里插入图片描述

其他补充

1、promise对象中有一个属性【PromiseResult】用来保存异步任务【成功/失败】的结果

2、promise中有那些方式修改promise对象的状态

let p = new Promise((resolve,reject) => {
	// 1、resolve函数
	resolve('ok'); // pending => fulfilled(resolved)
	// 2、reject函数
	reject('error'); // pending => rejected
	// 3、抛出错误
	throw '出问题了';
	// throw new error("出问题了");
})

3、一个promsie如果指定了多个成功/失败回调函数时,当promise改变时为对应状态时都会调用。

4、改变promise状态和指定回调函数谁先谁后?
(1)都有可能,正常情况下事先指定回调再去改变状态,但也可以先改变状态再去指定回调。
(2)如何先改变状态在指定回调?
1)在执行器中直接调用resolve()/reject()
2)延长更长时间才调用then()
(3)什么时候才能得到数据
1)如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据
2)如果先改变的状态,那当指定回调时,回调函数就会调用,得到数据

5、promise.then()返回的新promise的结果状态由什么决定?
简单来说就是由then()指定的回调函数的执行结果来决定
(1)如果抛出异常,新promise变为rejected,结果为抛出的异常
(2)如果返回的是非promise的任意值,新promise的结果变为resolved,结果为返回的值
(3)如果返回的是另一个新promise,此promise的结果就会称为新promise的结果

6、promise的异常穿透
当使用promise的then()进行链式调用时,可以在最后指定失败的回调,如果前面任何操作出了异常,都会传到最后失败的回调中处理。

7、如何中断promise链
有且只有一个方式,那就是返回一个pengding状态的promise对象

let p = new Promise((resolve,reject)=>{
	resolve('ok');
})

p.then(value => {
	console.log(111);
}).then(value => {
	console.log(222);
	// 中断链式调用
	return new Promise(()=>{});
}).then(value => {
	console.log(333);
}).catch(error => {
	console.warn(error);
})
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值