前言
笔记根据视频与PPT进行整理
【视频链接:尚硅谷Web前端Promise教程从入门到精通】
提取码:1234
字数4.5w+
1. Promise 简介
Promise 是一门新的技术(ES6 规范)
Promise 是 JS 中进行异步编程的新解决方案。(旧方案是单纯使用回调函数)
异步编程:
- fs 文件操作
- 数据库操作
- AJAX
- 定时器
旧方案中单纯使用回调函数,很可能会出现回调地狱(回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调执行的条件)的问题,不便于阅读,可读性差,不便于异常处理。
回调地狱:
Promise 支持链式调用, 可以解决回调地狱问题
2. Promise 基本使用
从语法上来说: Promise 是一个构造函数。
从功能上来说: promise 对象用来封装一个异步操作并可以获取其成功/失败的结果值。
2.1 Promise 基本使用体验
实现一个小功能:
点击按钮后显示是否中奖(30%概率中奖),若中奖弹出 “恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券” ,若未中奖弹出 “再接再厉”。
回调函数实现:
<!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> <div class="container"> <h2 class="page-header">Promise 初体验</h2> <button class="btn btn-primary" id="btn">点击抽奖</button> </div> <!-- 不使用Promise,使用回调函数实现 --> <script> //生成随机数 function rand(m,n){ return Math.ceil(Math.random() * (n-m+1)) + m-1; } /** 点击按钮后显示是否中奖(30%概率中奖) 若中奖弹出 恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券 若未中奖弹出 再接再厉 */ //获取元素对象 const btn = document.querySelector('#btn'); //绑定单击事件 btn.addEventListener('click', function(){ // 定时器 setTimeout(() => { //30% 1-100 取出一个数字,小于等于30==中奖 //获取从1 - 100的一个随机数 let n = rand(1, 100); //判断 if(n <= 30){ alert('恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券'); }else{ alert('再接再厉'); } }, 0); }) </script> </body> </html>
基于 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>Document</title> </head> <body> <div class="container"> <h2 class="page-header">Promise 初体验</h2> <button class="btn btn-primary" id="btn">点击抽奖</button> </div> <!-- 使用Promise实现 --> <script> //生成随机数 function rand(m,n){ return Math.ceil(Math.random() * (n-m+1)) + m-1; } /** 点击按钮后显示是否中奖(30%概率中奖) 若中奖弹出 恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券 若未中奖弹出 再接再厉 */ //获取元素对象 const btn = document.querySelector('#btn'); //绑定单击事件 btn.addEventListener('click', function(){ //Promise 形式实现 // Promise 构造函数接收的参数为一个函数(函数的参数为两个函数) // resolve 解决 函数类型的数据 // reject 拒绝 函数类型的数据 // 1) 创建 promise 对象(pending 状态), 指定执行器函数 const p = new Promise((resolve, reject)=>{ // 2) 在执行器函数中启动异步任务 setTimeout(() => { let n = rand(1, 100); //判断 // 3) 根据结果做不同处理 if(n <= 30){ // 如果成功了, 调用 resolve(), 指定成功的 value, 变为 resolved 状态 resolve(n); // 将 promise 对象的状态设置为 『成功』调用 resolve 函数 }else{ // 如果失败了, 调用 reject(), 指定失败的 reason, 变为 rejected 状态 reject(n); // 将 promise 对象的状态设置为 『失败』调用 reject 函数 } }, 0); }) //调用 then 方法 // 指定成功和失败要执行的函数 // 状态为成功,调用第一个函数,状态为失败,执行第二个函数 // 成功失败函数的参数 // value 值 成功函数参数 // reason 理由 失败函数参数 p.then((value) => { alert('恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券, 您的中奖数字为 ' + value); }, (reason) => { alert('再接再厉, 您的号码为 ' + reason); }); }) </script> </body> </html>
2.2 基于 promise 的形式读取文件
回调函数形式实现:
// 导入 fs 模块 const fs = require('fs'); // 回调函数的方式读取文件 fs.readFile( './content.txt', (err, data)=>{ if ( err ) throw err // 出错抛出错误 console.log( data.toString() ) //输出内容 } )
基于 Promise 实现:
// 导入 fs 模块 const fs = require('fs') // 基于promise读取文件 const p = new Promise( (resolve, reject)=>{ fs.readFile( './content.txt', (err, data)=>{ if ( err ) reject( err ) else resolve( data ) } ) } ) // 调用then处理读取文件的结果 p.then( value=>{ console.log( value.toString() ) }, reason=>{ console.log(reason) } )
2.3 基于 Promise 的 ajax 请求
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Promise 封装 AJAX</title> </head> <body> <div class="container"> <h2 class="page-header">Promise 封装 AJAX 操作</h2> <button class="btn btn-primary" id="btn">点击发送 AJAX</button> </div> <script> // 接口地址 https://api.liulongbin 跨域了 // https://www.escook.cn/api/get 跨域了 // 获取按钮 const btn = document.querySelector('#btn') btn.addEventListener( 'click', ()=>{ // 创建 Promise 对象 const p = new Promise( (resolve, reject)=>{ // 创建请求对象 const xhr = new XMLHttpRequest() // 初始化 xhr.open( 'GET', 'https://www.escook.cn/api/get' ) // 跨域了 // 设置请求头 // xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); // 发送请求 xhr.send() // 处理响应的结果 xhr.onreadystatechange = ()=>{ if ( xhr.readyState===4 ) { // 成功 if ( xhr.status>=200 && xhr.status<300 ) { // 请求响应信息 xhr.response resolve( xhr.response ) } else { // xhr.status 请求状态 reject( xhr.status ) } } } } ) p.then( value=>{ console.log( value ) }, reason=>{ console.log( reason ) } ) } ) </script> </body
请求失败,调用失败的处理函数,打印请求状态码
2.4 基于 Promise 封装 fs 模块读取文件
/** * 封装一个函数 mineReadFile 读取文件内容 * 参数: path 文件路径 * 返回: promise 对象 */ // 导入 fs const fs = require( 'fs' ) const mineReadFile = ( path )=>{ return new Promise( (resolve, reject)=>{ fs.readFile( path, ( err, data )=>{ if(err) reject(err) resolve(data) } ) } ) } // 调用读取文件的方法 // 指定对应的成功和失败的处理函数 mineReadFile( './content.txt' ) .then( val=>{ // 输出文件的内容 console.log( val.toString() ) }, reason=>{ // 输出错误信息 console.log( reason ) } )
2.5 util.promisify 方法封装转化成 Promise
/** * util.promisify 方法 */ // 引入 util 模块 const util = require( 'util' ) // 引入 fs 模块 const fs = require( 'fs' ) // 使用 util.promisify 方法封装 fs.readFile // 参数为一个函数 const mineReadFile = util.promisify( fs.readFile ) // 调用函数,并且指定对应的成功和失败处理函数 mineReadFile( './content.txt' ).then( val=>{ console.log( val.toString() ) }, reason=>{ console.log( reason ) } )
2.6 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.responseType = 'json' 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/getJok') .then(value => { console.log(value); }, reason => { console.warn(reason); }); </script> </body> </html>
依旧跨域问题,请求失败,调用失败的处理函数,打印请求状态码
2.7 promise 的状态
实例对象中的一个属性 『PromiseState』
- pending 未决定的
- resolved / fullfilled 成功
- rejected 失败
promise 的状态改变
- 成功 pending 变为 resolved / fullfilled
- 失败 pending 变为 rejected
说明:
只有这 2 种(只能pending 变为 resolved / fullfilled 或 pending 变为 rejected,没有其他的状态变化), 且一个 promise 对象只能改变一次,无论变为成功还是失败, 都会有一个结果数据,成功的结果数据一般称为 value, 失败的结果数据一般称为 reason。
2.8 promise 的基本流程
promise: 启动异步任务 => 返回promie对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定/多个) => 根据函数中执行成功或失败的结果,改变promise的状态 => 调用对应的成功或失败的函数。
3. Promise API
3.1 Promise 构造函数
Promise (excutor)
- (1) executor 函数: 执行器 (resolve, reject) => {}
- (2) resolve 函数: 内部定义成功时我们调用的函数 value => {}
- (3) reject 函数: 内部定义失败时我们调用的函数 reason => {}
executor 会在 Promise 内部立即同步调用,异步操作在执行器中执行,执行构造函数时会立即执行 executor 函数。
// 成功 // 创建Promise实例对象时同步执行传入的执行器函数 const p1 = new Promise( (resolve, reject)=>{ console.log( 'executor 执行' ) if(true) { resolve() } else { reject() } } ) console.log( p1 ) // 失败 const p2 = new Promise( (resolve, reject)=>{ console.log( 'executor 执行' ) if(false) { resolve() } else { reject() } } ) console.log( p2 )
3.2 Promise.prototype.then 方法
Promise.prototype.then 方法: (onResolved, onRejected) => {}
- (1) onResolved 函数: 成功的回调函数 (value) => {}
- (2) onRejected 函数: 失败的回调函数 (reason) => {}
指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调返回一个新的 promise 对象
// 成功 const p1 = new Promise( (resolve, reject)=>{ resolve('ok') } ) // 执行第一个函数 -- 成功的回调函数 p1.then( val=>{ console.log(val) }, reason=>{ console.log(reason) } ) // 失败 const p2 = new Promise( (resolve, reject)=>{ reject('Error') } ) // 执行第二个函数 -- 失败的回调函数 p2.then( val=>{ console.log(val) }, reason=>{ console.log(reason) } )
3.3 Promise.prototype.catch 方法
Promise.prototype.catch 方法: (onRejected) => {}
- onRejected 函数: 失败的回调函数 (reason) => {}
then()的语法糖, 相当于: then(undefined, onRejected)
catch 定义用于处理失败的回调函数,不定义成功的回调函数
const p = new Promise( (resolve, reject)=>{ reject('error') } ) // Promise的状态为失败时才调用catch中的回调函数 p.catch( reason=>{ console.log(reason) } ) const pp = new Promise( (resolve, reject)=>{ resolve('ok') } ) // Promise的状态为失败时才调用catch中的回调函数 pp.catch( reason=>{ console.log(reason) } )