Promise 详解

示例

在进行 Promise 的讲解前,请先通过两个小示例来感受一下 Promise。

一个抽奖小游戏

原生 JavaScript 实现
<!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>
    <link rel="stylesheet" href="./css/index.css">
</head>
<body>
    <button id="start">Start</button>    

    <script src="./index.js"></script>
</body>
</html>
*{
    /* 消除浏览器样式默认存在的内外边距 */
    margin: 0px;
    padding: 0px;
    box-sizing: border-box;
}


body{
    /* 
    设置 body 元素的最低高度为浏览器可视区域的高度,
    并为该标签设置了 flex 布局。
    */
    min-height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
}


/* 为按钮设置样式 */
#start{
    width: 130px;
    height: 40px;
    border: 1px solid yellow;
    background-color: red;
    color: #fff;
    font-size: 600;
    font-weight: 700;
    border-radius: 3px;
    /* 为按钮设置过渡动画效果,过渡时间为 1s */
    transition: all 1s;
}


/* 为按钮添加鼠标悬停效果 */
#start:hover{
    cursor: pointer;
    width: 200px;
}
function rand(min, max) {
    // 随机数生成函数
    // 该函数将返回一个位于区间 [min, max) 的随机数(包括 min 但不包括 max)
    return Math.floor(Math.random() * (max - min) + min);
}


// 获取 DOM 元素
const btn = document.querySelector('#start');


// 为 DOM 元素添加点击事件,为模拟抽奖过程中存在的延迟我们的事件调用函数
// 是一个 setTimeout() 函数,我们为这个定时器函数设置了 1s 的延迟。
btn.addEventListener('click', () => {
    setTimeout(() => {
        if(rand(1, 100) > 30){
            alert('恭喜你中奖了!')
        }else{
            alert('很遗憾,请再接再厉!')
        }
    }, 1000)
}
)
Promise 实现

Promise 实现与原生 JavaScript 实现的不同点仅位于 JavaScript 代码部分。

function rand(min, max) {
    // 随机数生成函数
    // 该函数将返回一个位于区间 [min, max) 的随机数(包括 min 但不包括 max)
    return Math.floor(Math.random() * (max - min) + min);
}


// 获取 DOM 元素
const btn = document.querySelector('#start');


// 为 DOM 元素添加点击事件,为模拟抽奖过程中存在的延迟我们的事件调用函数
// 是一个 setTimeout() 函数,我们为这个定时器函数设置了 1s 的延迟。
btn.addEventListener('click', () => {
    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            if(rand(1, 100) > 30){
                resolve() // 成功执行后需要执行的函数,该函数执行后将 Promise 对象的状态设置为 【成功】
            }else{
                reject()  // 成功执行后需要执行的函数,该函数执行后将 Promise 对象的状态设置为 【失败】
            }
        }, 1000)
    })
    
    p.then(() => {
        alert('恭喜你中奖了!')
    }, 
    () => {
        alert('很遗憾,请再接再厉!')
    })  
    // then() 方法中的第一个参数为成功执行时需要调用的函数,第二个参数为失败执行时需要调用的函数
})

读取文件

原生 JavaScript 实现

由于读取文件需要使用 fs 模块,我们需要通过 Node.js 来运行该示例文件。如果你在浏览器中导入该示例文件很可能会在控制台中看到如下错误:

require

让我们看看被读取文件中的内容:

                    《也是微云》
                        胡适
            也是微云,也是微云过后月光明。
            只不见去年的游伴,只没有当日的心情。
            不愿勾起相思,不敢出门望月。
            偏偏月进窗来,害我相思一夜。
// 导入 fs 模块
const fs = require('fs');


fs.readFile('./content.txt', (err, data) => {
    if(err) throw(err);
    // 由于读取的结果为 buffer 对象,所以我们需要通过
    // toString() 函数来将该 buffer 对象转换为字符串。
    console.log(data.toString())
})

在终端中 cd 进入该示例文件后,使用如下命令运行该文件(需先安装 Node.js):

node index.js
Promise 实现
const fs = require('fs');


fs.readFile('../content.txt', (err, data) => {
    const p = new Promise((resolve, reject) => {
        if(err) reject(err);
        resolve(data);
    })

    p.then((value) => {
        console.log(value.toString())
    },
    (reason) => {
        throw(reason)
    }
    )
})

同样,执行该文件需要在终端使用 cd 命令切换到到文件所在的路径后使用如下命令(需要先安装并配置 Node.js):

node index.js

Promise 对象

Promise 对象的状态

Promise 对象代表一个异步操作,该对象存在三种状态,对象的状态不受外界影响。对象的状态仅被异步操作的结果影响,任何其他操作都无法改变这个状态,这也是 Promise 这个名字的由来。

Promise 对象的状态由其 PromiseState 属性进行记录。

状态值描述
pending初始状态,执行的操作既没有被拒绝也没有被同意。
rejected已拒绝,执行操作失败。
fulfilled已同意,执行操作成功。

状态的变换仅存在两种可能:

  1. pending 变为 rejected
  2. pending 变为 fulfilled

注:

  1. 状态的转换仅能发生一次。状态由 pending 转换为 rejected 或 fulfilled 后将无法再发生改变。
  2. 对象的状态将决定后续调用的回调函数具体为哪一个。
  3. 若 Promise 对象的状态由 pending 转换为 rejected 而你没有对此进行捕获(设置 rejected 状态下需要执行的回调函数),则浏览器将抛出错误。类似于这样:
    打印结果
    可以通过使用 Promise.catch() 方法来对该状态进行捕获。

Promise 对象的创建

Promise 对象可以通过 new 来调用 Promise() 构造函数来进行创建。

const p = new Promise((resolve, reject) => {
	if(1==1):
		resolve()
	else: 
		reject()
})

其中:

  1. 构造函数 Promise() 接收一个参数,你可以通过使用该参数提供一个函数来指定需要执行的 执行器函数
    执行器函数的第一个参数所对应的函数用于将 Promise 对象的状态由 pending 转换为 fulfilled。而第二个参数所对应的函数用于将 Promise 对象由 pending 转换为 rejected
  2. resolve()reject() 函数的调用仅改变创建的 PromisePromiseState 属性的值,并不会中止执行器函数的运行。
    在下面的这个例子中,Win 将被成功打印:
<!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>Demo</title>
</head>
<body>
    <script>
        const p = new Promise((resolve, reject) => {
	        if(1==1){
		        resolve()
                console.log('Win')
            }
	        else{
                reject()
            }
        })
    </script>
</body>
</html>
  1. Promise 对象创建的过程中会调用作为参数传递给 Promise 构造函数的函数,所以应该在合适的地方(比如某个事件调用函数中)创建 Promise 对象。
Promise 对象的状态的改变
pending 转换为 fulfilled

通过调用提供给 Promise 构造函数的函数的第一个参数对应的函数 resolve() 即可将创建的 Promise 对象的状态由 pending 转换为 fulfilled

pending 转换为 rejected
  1. 通过调用提供给 Promise 构造函数的函数的第二个参数对应的函数 reject() 即可将创建的 Promise 对象的状态由 pending 转换为 rejected
  2. 通过在提供给 Promise 构造函数的函数中使用关键字 throw 抛出错误也可以将 Promise 对象的状态属性 PromiseStatepending 转换为 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>Demo</title>
</head>
<body>
    
    <script>
        const p = new Promise((resolve, reject) => {
	        if(1==1){
                throw 'Error';
            }
        })

        console.log(p)
    </script>
</body>
</html>

打印结果:

打印结果

与 Promise 对象相关的 API

Promise.then()

Promise.then() 接收两个参数,其中第一个参数为 Promise 对象的状态为 fulfilled 时执行的回调函数;第二个参数为 Promise 对象的状态为 rejected 时执行的回调函数。

Promise.catch()

Promise.catch() 与 Promise.then() 类似,Promise.catch() 仅接收一个参数,你可以为该参数提供一个实参(函数),用于指定 Promise 对象的状态为 rejected 时执行的调用函数。

Promise.resolve()

Promise.resolve() 方法用于将基本数据类型或其他引用数据类型转换为 Promise 对象。对于 Promise.resolve() ,存在如下关系:

Promise.resolve('RedHeart');

// 等价于

new Promise((resolve) => {resolve('RedHeart')});
验证
<!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>Demo</title>
</head>
<body>
    
    <script>
        const p1 = Promise.resolve('RedHeart');
        const p2 = new Promise((resolve) => {resolve('RedHeart')});

        console.log(p1);
        console.log(p2);
    </script>
</body>
</html>

打印结果:

打印结果

Promise.reject()

Promise.reject() 方法用于将基本数据类型或其他引用数据类型转换为 Promise 对象。对于 Promise.reject(), 存在如下关系:

Promise.reject('RedHeart');

// 等价于

new Promise((resolve, reject) => {reject('RedHeart')});
验证
<!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>Demo</title>
</head>
<body>
    
    <script>
        const p1 = Promise.reject('RedHeart');
        const p2 =new Promise((resolve, reject) => {reject('RedHeart')});
        
        console.log(p1);
        console.log(p2)
    </script>
</body>
</html>

打印结果:

打印结果

转换为 Promise 对象的规则

在使用 Promise.then()、Promise.reject() 等函数时可能会将其他数据转换为 Promise 对象,转换过程中将遵循一定的转换规则。

Promise 对象转换为 Promise 对象

如果被转换的 Promise 对象的状态结果为 rejected ,则转换后的 Promise 对象的状态也将为 rejected;如果被转换的 Promise 对象的状态结果为 fulfilled ,则转换后的 Promise 对象的状态也将为 fulfilled

<!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>Demo</title>
</head>
<body>
    
    <script>
        const p1 = Promise.resolve(new Promise((resolve, reject) => {
            resolve('RedHeart')
        }));

        const p2 = Promise.resolve(new Promise((resolve, reject) => {
            reject('RedHeart')
        }));

        
        console.log(p1);
        console.log(p2)
    </script>
</body>
</html>

打印结果:
打印结果
注:
若 Promise.then() 的调用函数中使用 retrun 返回了一个 Promise 对象,则 Promise.then() 返回的 Promise 对象依据此类情况(Promise 对象转换为 Promise 对象)进行判断。

非 Promise 数据转换为 Promise 对象

非 Promise 对象转换为 Promise 对象后的 Promise 对象的结果存在以下情况:

  1. 经 Promise.resolve() 转换后得到的 Promise 对象的状态为 fulfilled;而经 Promise.reject() 转换后得到的 Promise 对象的状态为 rejected。Promise.resolve() 及 Promise.reject() 得到的 Promise 对象的 PromiseResult 属性值均为提供给他们各自的参数。
  2. Promise.then() 及 Promise.catch() 的转换结果均由其调用的回调函数来决定。但对于非 Promise 数据转换为 Promise 对象这种情况,得到的 Promise 对象的状态都将为 fulfilled,其 PromiseResult 属性的值则由其调用的回调函数的返回值决定。
验证
<!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>Demo</title>
</head>
<body>
    
    <script>
        const p1 = Promise.resolve('RedHeart');
        const p2 = Promise.reject('RedHeart');
        var p3 = new Promise((resolve, reject) => {
            resolve('RedHeart')
        });
        var p4 = new Promise((resolve, reject) => {
            reject('RedHeart')
        });

        p3 = p3.then((value) => {
            return 'p3'
        });

        p4 = p4.catch((reason) => {
            return 'p4'
        });

        console.log(p1);
        console.log(p2);
        console.log(p3);
        console.log(p4);
    </script>
</body>
</html>

打印结果:

打印结果

抛出错误

  1. 经 Promise.resolve() 转换后得到的 Promise 对象的状态为 fulfilled;而经 Promise.reject() 转换后得到的 Promise 对象的状态为 rejected。Promise.resolve() 及 Promise.reject() 得到的 Promise 对象的 PromiseResult 属性值均为提供给他们各自的参数。
  2. 若回调函数中抛出错误,则 Promise.then() 及 Promise.catch() 产生的 Promise 对象的状态均为 rejected。而他们的 PromiseState 属性值为调用函数中抛出错误时提供的信息。
验证
<!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>Demo</title>
</head>
<body>
    
    <script>
        const p1 = Promise.resolve(() => {
            throw 'Error'
        });
        const p2 = Promise.reject(() => {
            throw 'Error'
        });
        
        var p3 = new Promise((resolve, reject) => {
            resolve()
        });
        var p4 = new Promise((resolve, reject) => {
            reject()
        });

        p3 = p3.then(() => {
            throw 'Error'
        });
        p4 = p4.catch(() => {
            throw 'Error'
        });


        console.log(p1);
        console.log(p2);
        console.log(p3);
        console.log(p4);
    </script>
</body>
</html>

打印结果:
执行结果

Promise 中的链式调用

链式调用

由于 Promise.then() 及 Promise.catch() 等函数的返回结果仍旧为 Promise 对象,所以我们可以像这样去使用 Promise.then() 的返回结果:

<!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>Demo</title>
</head>
<body>
    
    <script>
        const p = new Promise((resolve, reject) => {
            resolve()
        });

        p.then(() => {
            console.log('1')
        }).then(() => {
            console.log('2')
        }).then(() => {
            console.log('3')
        })
    </script>
</body>
</html>

打印结果:
打印结果z

注:

  1. 链式调用过程中的调用结果(由得到的 Promise 对象的 PromiseStatePromiseResult 属性决定)需要结合前面讲到的 转换为 Promise 对象的规则 来进行判断。

猜猜看

请观察如下代码并猜测最后两个打印语句的打印结果。

<!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>Demo</title>
</head>
<body>
    
    <script>
        const p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('OK')
            }, 1000)
        });

        p.then(value => {
            return new Promise((resolve, reject) => {
                resolve('Win')
            })
        }).then(value => {
            console.log(value)
        }).then(value => {
            console.log(value)
        })
    </script>
</body>
</html>

打印结果:

Win
undefined

分析
  1. 第一个 Promise.then()
    由于第一个 Promise.then() 的返回值为一个 Promise 对象,所以我们需要先判断这个 Promise 对象的 PromiseStatePromiseResult 的值,这两个属性对应的值分别为 fulfilledWin。因此第一个 Promise.then() 返回的 Promise 对象的 PromiseStatePromiseResult 的值分别为 fulfilledWin
  2. 第二个 Promise.then()
    由于第一个 Promise.then() 返回的 Promise 对象的 PromiseStatePromiseResult 的值分别为 fulfilledWin。因此传递给第二个 Promise.then() 的 value 对应的值为 Winfulfilled 决定能不能调用第二个 Promise.then() 函数,如果第一个 Promise.then() 的 PromiseState 对应的值为 rejected,则第二个 Promise.then() 将不会被调用。
  3. 第三个 Promise.then()
    由于第二个 Promise.then() 发生了 非 Promise 数据转换为 Promise 对象,所以第二个 Promise.then() 的结果为 Promise 对象,该对象的PromiseStatePromiseResult 的值分别为 fulfilledundefined

异常穿透

我们可以在链式调用的最后一环添加一个 Promise.catch() 函数以应对可能产生的 rejected 状态的 Promise 对象。就像这样:

<!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>Demo</title>
</head>
<body>
    
    <script>
        const p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('OK')
            }, 1000)
        });

        p.then(value => {
            return new Promise((resolve, reject) => {
                reject()
            })
        }).then(value => {
            console.log(value)
        }).then(value => {
            console.log(value)
        }).catch(reason => {
            console.log('Lose')
        })
    </script>
</body>
</html>

打印结果:

Lose

中断

对于下面的代码,如果你希望打印的结果为:

1
2

而不是:

1
2
3

你该怎么做?

<!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>Demo</title>
</head>
<body>
    
    <script>
        const p = new Promise((resolve, reject) => {
            resolve()
        });

        p.then(() => {
            console.log(1)
        }).then(() => {
            console.log(2)
        }).then(() => {
            console.log(3)
        })
    </script>
</body>
</html>

我们可以尝试在第二个 Promise.then() 中的调用函数中返回一个特定状态(在这个例子中需要的 Promise 对象的状态应该为 pendingrejected)的 Promise 对象来中断链式调用。

<!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>Demo</title>
</head>
<body>
    
    <script>
        const p = new Promise((resolve, reject) => {
            resolve()
        });

        p.then(() => {
            console.log(1)
        }).then(() => {
            console.log(2)
            return new Promise((resolve, reject) => {
                reject()
            })
        }).then(() => {
            console.log(3)
        })
    </script>
</body>
</html>

打印结果:

打印结果

使用如下代码也可以在适当的时机中断链式调用:

<!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>Demo</title>
</head>
<body>
    
    <script>
        const p = new Promise((resolve, reject) => {
            resolve()
        });

        p.then(() => {
            console.log(1)
        }).then(() => {
            console.log(2)
            return new Promise(() => {})
        }).then(() => {
            console.log(3)
        })
    </script>
</body>
</html>

打印结果:

1
2

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BinaryMoon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值