Skip to content

Promise

一、概念

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

Promise是ES6新增的引用类型,语法上 Promise 是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果,可以通过new来进行实例化对象。Promise内部包含着异步的操作。

是一个专门用来解决异步 回调地狱 问题的。

二、回调函数

回调函数就是一个被作为参数传递的函数。

js
// 把函数 b 当作参数传递到 函数 a 中
// 在函数 a 中以行参的方式进行调用
function a(cb) {
  cb()
}

function b() {
  console.log('我是函数 b')
}

a(b)

为什么需要回调函数

  • 当我们执行一个异步的行为的时候,我们需要在一个异步行为执行完毕之后做一些事情
  • 那么,我们是没有办法提前预知这个异步行为是什么时候完成的
  • 我们就只能以回调函数的形式来进行
  • 我们并不知道 ajax 请求什么时候完成,所以就要以回调函数的形式来进行

三、回调地狱

  • 当一个回调函数嵌套一个回调函数的时候

  • 就会出现一个嵌套结构

  • 当嵌套的多了就会出现回调地狱的情况

  • 比如我们发送三个 ajax 请求

    • 第一个正常发送
    • 第二个请求需要第一个请求的结果中的某一个值作为参数
    • 第三个请求需要第二个请求的结果中的某一个值作为参数
    javascript
    ajax({
      url: '我是第一个请求',
      success (res) {
        // 现在发送第二个请求
        ajax({
          url: '我是第二个请求'
          data: { a: res.a, b: res.b },
          success (res2) {
            // 进行第三个请求
            ajax({
              url: '我是第三个请求',
              data: { a: res2.a, b: res2.b },
      				success (res3) { 
                console.log(res3) 
              }
            })
          }
        })
      }
    })
  • 类似以上深度嵌套的回调通常被称为**「回调地狱」**,因为它让代码可读性变差且不易维护

  • 当代码成为这个结构以后,已经没有维护的可能了

三、promise

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数

此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

Promise将 回调转换成链式调用来解决回调地狱

1.状态

promise 就是一个语法,有三种状态:

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)

promise 意思是表示“承诺”,表示其他手段无法改变。

一旦状态确定,就不会再变,任何时候都可以得到这个结果。

Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected只要这两种情况发生,状态就不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。

2.语法

Promise 构造函数

js
new Promise((resolve, reject) => {
    if(成功){
        resolve(value)
    }else if(失败){
        reject(reason)
    }
})
  • Promise接收一个函数作为参数,这个函数接收两个回调函数,分别为resolvereject
  • 当异步请求成功,就调用resolve(),并且将promise 的状态改为fulfilled(已成功)
  • 当异步请求失败,就调用reject(),并且将promise 的状态改为rejected(已失败)
  • resolve()和reject()括号里都可以传递形参

Promise请求流程

js
const p1= new Promise(function (resolve, reject) {
  // resolve() 表示成功的回调
  // reject() 表示失败的回调
})

p1.then(function (res) {
  // 成功的函数
}).catch(function (err) {
  // 失败的函数
})

栗子

js
let p1 = new Promise((resolve, reject) => {
    var num = Math.random()
    console.log(num)
    if(num>0.5){
    	resolve()
    }else{
    	reject()
    }
});

p1.then(function(){
    console.log("成功了")
}).catch(function(){
    console.log("失败了")
})

四、promise 原型方法

添加在 Promise对象原型上的方法

1.then()

js
//原型方法
Promise.prototype.then( )

1.一个参数

  • 当promise 的状态为成功fulfilled,就会调用此方法

  • 当使用一个参数写法时,请求失败的操作就可以用catch()完成

js
p.then(()=>{...})

2.两个参数

  • 第一个回调函数接收一个参数,当promise 的状态为成功,就调用第一个回调函数

    • 这个参数是异步请求成功后传回的数据
  • 第一个回调函数接收一个参数,当promise 的状态为失败,就调用第一个回调函数,一般不这么做

    • 这个参数是异步请求失败的信息
js
p.then((value)=>{...},(reason)=>{...})

2.catch()

js
//原型方法
Promise.prototype.catch()
  • 用于指定发生错误时的回调函数
  • 当promise 的状态为失败rejected,就会调用此方法
js
p.catch((reason)=>{
    console('请求失败'+reason)
})

3.finally()

js
Promise.prototype.finally()

用于指定不管 Promise 对象最后状态成功或失败,都会执行的操作

五、promise 静态方法

添加在 promise函数上的方法

1.Promise.resolve(value)

  • 如果传入的参数为非promise类型的对象,则返回的结果为成功promise对象
  • 如果传入的参数为 Promise对象,则resolve()的结果和参数的结果一致
js
Promise.resolve()
.then(function(){
	console.log('成功了');	//会调用then
}).catch(function(){
	console.log('失败了')
})

2.Promise.reject(reason)

  • reason: 失败的原因
  • 说明: 返回一个失败的 promise 对象,返回的结果都是失败的
js
Promise.reject()
.then(function(){
    console.log('成功了');
}).catch(function(){
    console.log('失败了')	//会调用catch
}

3.Promise.all()

用法
  • Promise.all()方法接收的参数是一个数组,数组中的每一项是一个个的Promise对象

  • promise.all()数组里所有promise都完成后才会resolve,否则reject

  • promise.all()返回的结果可以用then()方法取出,也可以用async/await

成功的结果

数组里的promise全部成功才返回resolve()的结果,并以数组形式呈现,数组中的每一项对应的就是 .all数组中的每一项的请求结果返回的值

js
const p1=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(1)
    },100)
})

const p2=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(2)
    },200)
})

const p3=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(3)
    },1000)
})

Promise.all([p1,p2,p3]).then(res=>{			// [ 1, 2, 3 ]
    console.log(res);			
})
失败的结果

有一个失败就返回reject()的结果,并且只返回第一个失败的promise

js
const p1=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(1)
    },100)
})

const p2=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject(3)
    },200)
})

const p3=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject(4)
    },100)
})


Promise.all([p1,p2,p3]).then(res=>{
    console.log(res);
}).catch(res=>{
    console.log(res);
})

4.Promise.race()

用法
  • Promise.race()方法接收的参数是一个数组,数组中的每一项是一个个的Promise对象
返回的结果
  • promise.race()取最快返回的结果
  • promise.race()返回的结果可以用then()方法取出,也可以用async/await
js
const p1=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(1)
    },100)
})

const p2=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(2)
    },200)
})

Promise.race([p1,p2]).then(res=>{
    console.log(res);
})

5.Promise.allSettled()

用法
  • Promise.allSettled()方法接收的参数是一个数组,数组中的每一项是一个个的Promise对象
返回的结果
  • Promise.allSettled()会返回数组里所有promise的结果,以数组的形式呈现,数组里是一个个对象,包含每个promise的返回状态和结果
js
const p1=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(1)
    },100)
})

const p2=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(2)
    },200)
})

const p3=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject(3)
    },100)
})

Promise.allSettled([p1,p2,p3]).then(res=>{
    console.log(res);
})

// [
//     { status: 'fulfilled', value: 1 },
//     { status: 'fulfilled', value: 2 },
//     { status: 'rejected', reason: 3 }
// ]

6.Promise.any()

用法
  • Promise.allSettled()方法接收的参数是一个数组,数组中的每一项是一个个的Promise对象
返回的结果
  • 数组中只要有成功promise就返回成功的结果,并且返回最快promise结果
  • 数组中所有promise都失败,才会返回失败的结果
成功
js
const p1=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(1)
    },300)
})

const p2=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(2)
    },200)
})

const p3=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject(4)
    },100)
})


Promise.any([p1,p2,p3]).then(res=>{			// 2
    console.log(res);
}).catch(res=>{
    console.log(res);
})
全部失败
js
const p1=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject(1)
    },300)
})

const p2=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject(2)
    },200)
})

const p3=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject(4)
    },100)
})


Promise.any([p1,p2,p3]).then(res=>{	
    console.log(res);
}).catch(res=>{
    console.log(res);;
})
// [AggregateError: All promises were rejected] { [errors]: [ 1, 2, 4 ] }

六、Promise关键问题

  1. 如何改变 promise 的状态?

    • resolve(value): 如果当前是 pending 就会变为 resolved
    • reject(reason): 如果当前是 pending 就会变为 rejected
    • 抛出异常: 如果当前是 pending 就会变为 rejected
    js
    let p=new Promise((resolve,reject)=>{
    //1.resolve 函数
    	//resolve('ok')   //pending=>fulfilled(resolve)
    //2.reject 函数
    	//reject('error')	//pending=>rejected
    //3.抛出错误    
    	throw  'error'
    })
  2. 改变 promise 状态和指定回调函数谁先谁后?

    • 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
    • 如何先改状态再指定回调?
      • 在执行器中直接调用 resolve()/reject()
      • 延迟更长时间才调用 then()
    • 什么时候才能得到数据?
      • 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
      • 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
  3. promise.then()返回的新 promise 的结果状态由什么决定?

    • 简单表达:
    • 详细表达:
      • 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常
      • 如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
      • 如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果
    js
    let p=new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve('ok')
        },1000)
    })
    
    p.then(value=>{
        return new Promise((resolve, reject) => {
            resolve('success')
        });
    }).then(value=>{
        console.log(value);  //success
    }).then(value=>{
        console.log(value);  //undefine,因为上一个then没有返回值
    })
  4. promise 如何串连多个操作任务?

    • promise 的 then()返回一个新的 promise, 可以开成 then()的链式调用
    • 通过 then 的链式调用串连多个同步/异步任务
  5. promise 异常传透?

    • 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回
    • 前面任何操作出了异常, 都会传到最后失败的回调中处理
  6. 中断 promise 链?

    • 当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数
    • 办法: 在回调函数中返回一个 pendding 状态的 promise 对象

七、自定义封装Promise

Promise封装ajax

js
function $ajax(path){
    return new Promise((resolve,reject)=>{
        const xhr=new XMLHttpRequest()
        xhr.open('get',path)
        xhr.send()
        xhr.onreadystatechange=function(){
            if(xhr.readyState==4){
                if(xhr.status==200){
                    resolve(xhr.responseText)
                }else{
                    reject(xhr.status)
                }
            }
        }
    })
}

$ajax('https://api.mcloc.cn/love')
.then(value=>{
	console.log(value);
},reason=>{
	console.warn(reason);
})

Promise封装fs请求

js
function mineReadFile(path){
    return new Promise((resolve,reject)=>{
        require('fs').readFile(path,(err,data)=>{
            if(err) reject(err)
            resolve(data.toString())
        })
    })
}

mineReadFile(require('path').join(__dirname,'/content.txt')).then(value=>{
    console.log(value);
},reason=>{
    console.log(reason);
})

八、async和await

  • async/await其实是Promise的语法糖,它能实现的效果都能用then链来实现,它是为优化then链而开发出来的
  • async是“异步”的简写,await译为等待,所以async声明function是异步的
  • 由async修饰的函数,返回的都是一个promise

语法:

  • async修饰函数,await修饰Promise

    • async修饰的函数,返回的都是一个Promise
    • await可以把Promise的结果直接取出来
  • await可以取出Promise的返回值

js
const fun=async()=>{
	let res=await new Promise((resolve)=>{
        resolve(1)
    })
    console.log(res)
}

fun()

MIT Licensed