Featured image of post JavaScript 异步实现方案Promise

JavaScript 异步实现方案Promise

异步实现方案Promise

promise是什么?干什么用?

promise是异步编程的解决方案,原本的异步编程采用回调函数的方式获取返回的数据,在方法中传入一个回调函数当参数,当异步获取数据的时候调用这个回调函数并把数据传入,就可以获取数据。但是当一个回调函数嵌套另一个回调函数的时候,会出现回调地狱。

promise就是主要解决回调地狱的问题,让代码编写看起来像同步。

Promise是一个构造函数,通过new关键字实例化一个promise

1
2
3
4
const p = new Promise((resolve,reject)=>{
        console.log(1111);
       })
console.log(p);

Alt text

可以看出new的时候会执行Promise中的函数,打印p实例PromiseState是实例pending状态

Promise对象的两个属性:

1.状态(PromiseState):pending表示进行中,fulfilled(已成功),rejest(已失败)

2.结果(PromiseResut):保存resolve或reject函数执行时传递的参数

1
2
3
const p = new Promise((resolve,reject)=>{
        resolve("执行成功的回调")
       })

Alt text 当执行resolve函数时候,传递的参数会保存在PromiseResult中,并传递出去,在后面的then方法中调用第一个函数参数就是这个传递过来的值。实例的状态改为fulfilled(已成功)

当执行reject函数,传递的参数会保存在PromiseResult 中,并把错误作为参数传递出去。在后面的then方法的第二个函数中或catch方法里可以获取这个错误信息。实例的状态改为rejected

1
2
3
4
const p = new Promise((resolve,reject)=>{
        reject("发生错误")
       })
       console.log(p);

Alt text

当在调用then方法获取err后,PromiseResult为变为undefined

1
2
3
4
5
6
const p = new Promise((resolve,reject)=>{
        reject("发生错误")
       }).then(()=>{},(err)=>{
        console.log(err);
       })
       console.log(p);

Alt text

1.2 then方法

promise的then方法接受两个回调函数作为参数,第一个回调函数是Promise状态为fulfilled时候的会调用,第二个回调函数是Promise状态是reject时候会调用。在通常情况下第二个函数可以不写,用catch方法同样可以捕获错误!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const p = new Promise((resolve,reject)=>{
        console.log(res);
      //这里未定义使用变量会抛出错误,同样会让promise的状态变为rejected,catch就会捕获   
       })
       p.then((data)=>{
        console.log(data);
       }).catch(err=>{
        console.log(err);
       })
       console.log(p);

Alt text

如果函数的返回值是一个promise就可以用then方法。

问题1. 如果Promise的状态没有发生改变,then()里的方法不会执行

示例:

1
2
3
4
5
6
7
8
const p = new Promise((resolve,reject)=>{

        })
        p.then((data)=>{
            console.log("成功",data); //p的promiseState状态为pending 此时这个回调函数不会执行,
        }).catch(err=>{
            console.log("错误");
        })

以上代码并不会输出任何语句。

示例2:

1
2
3
4
5
6
7
8
const p = new Promise((resolve,reject)=>{
            console.log(x); //x没有定义,这里会抛出异常,抛出一个异常会把promiseState变为reject,会被catch捕获
        })
        p.then((data)=>{
            console.log("成功",data); //p的promiseState状态为pending 此时这个回调函数不会执行,
        }).catch(err=>{
            console.log(err);
        })

Alt text

如果没有catch方法会在浏览器抛出错误

Alt text

问题2 在then方法中,通过return将返回的Promise实例改为fulfilled状态

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 在then()方法中使用return 一个值或promise,会把状态改为fulfilled,返回的值会传递给下面的then方法
        const p = new Promise((resolve,reject)=>{
            resolve()
        })
        const t = p.then(value=>{   
            return 123     // 通过return 123 t的状态改为fulfilled,t.then()就会执行,打印123
        }).then(value=>{
            console.log(value);
        }).catch(err=>{
            console.log(err);
        })

Alt text 看下t的状态

Alt text

1.4 Promise.prototype.catch()

catch什么时候会执行

  1. 当Promise的状态改为rejected的时候
1
2
3
4
const p = new Promise((resolve,reject)=>{
           throw new Error('错误')    //主动抛出个错误,p的状态改为rejected,相当于reject('错误')
        }).then(data=>console.log(data))
          .catch(err=>console.log(err))
  1. 当Promise中的代码执行发生错误的时候,执行catch
1
2
3
4
const p = new Promise((resolve,reject)=>{
            console.log(x);  // x没有被定义,抛出引用错误,将会被catch捕获打印出来
        }).then(data=>console.log(data))
          .catch(err=>console.log(err))

1.5 Promise.resolve()

此方法现有对象转为promise对象。

1
2
3
4
5
6
7
8
      var  p = Promise.resolve('hello')   //将hello转为
        //等价于
        const p = new Promise(resolve=>{
            resolve("hello")
        })
        p.then(data=>{
            console.log(data);
        })

参数’hello’是一个原始值,Promise.resolve()返回一个新的Promise对象,状态为resolved,p.then()方法将会执行。data就是hello。

1.6 Promise.all()

Promise.all方法用于将多个Promise实例包装成一个新的Promise实例。

1
const p = Promise.all([p1,p2,p3])

新的Promise实例P的状态有p1,p2,p3决定,有两种情况

  1. p1,p2,p3的状态都是fulfilled,p的状态才会是fulfilled,p1,p2,p3的返回值组成一个数组,传递给P的.then()中的成功回调函数。
  2. p1,p2,p3中只要有个状态为rejected,p的状态就变成rejected,此时第一个rejected的实例的返回值会传递给p的catch中err回调函数。

示例1:p1,p2,p3的状态都是fulfilled

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const p1 = new Promise((resolve,reject)=>{   // p1的状态是fulfilled
   resolve('hello')
})
const p2=  new Promise((resolve,reject)=>{   // p2 的状态是fulfilled
    resolve("world")
})
const p3=  new Promise((resolve,reject)=>{    // p3的状态是fulfilled
    resolve("!!!")
})
Promise.all([p1,p2,p3]).then(data=>{         // p1,p2,p3都是fulfilled,才会执行then方法成功的回调
    console.log(data.join(' '));
}).catch(err=>{
    console.log(err);
})

Alt text

示例2:有一个状态是rejected,这个实例的返回值会给catch捕获

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const p1 = new Promise((resolve,reject)=>{
   resolve('hello')
})
const p2=  new Promise((resolve,reject)=>{    // p2 的状态是reject ,p将会执行catch的回调得到它的返回值
    reject("发生错误")
})
const p3=  new Promise((resolve,reject)=>{
    resolve("!!!")
})
Promise.all([p1,p2,p3]).then(data=>{
    console.log(data.join(' '));
}).catch(err=>{
    console.log(err);
})

Alt text

1.7 Promise.race()

Promise.race()同样是将多个Promise实例包装成一个新的Promise实例。

1
const p = Promise.race([p1,p2,p3])

上面代码中,只要p1,p2,p3中的一个有状态发生改变,p的状态就跟着改变。谁先改变状态,谁的值就会传递给p的回调函数。先到先得。race比赛的意思,字面意思就是谁先到就有优先。而不管是resolve还是reject。

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
const p1 = new Promise((resolve,reject)=>{
        return $.ajax({
            method:'get',
            url:'data1.json',
            success:function(res){
                resolve(res)
            }
        })
    })
    const p2 = new Promise((resolve,reject)=>{
        setTimeout(() => {
            reject(new Error('错误'))
        },9);    // 这里设置p2 实例9毫秒后改变状态为reject,如果ajax请求没有在9秒内执行resolve函数,p2实例
									// 会先改变为reject状态,此时p会执行catch函数中的回调函数,打印 ‘错误’
    })
    const p = Promise.race([p1,p2])
        .then(data=>{
        console.log(data);
        })
        .catch(err=>{
            console.log(err);
        })

Alt text

测试<10毫秒ajax不会返回请求,所以p2状态先改变,p2一改变状态,p就跟着改变,p2的返回值就传递给p的回调函数。打印出 ‘错误’

修改时间为≥10毫秒

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const p1 = new Promise((resolve,reject)=>{
        return $.ajax({
            method:'get',
            url:'data1.json',
            success:function(res){
                resolve(res)
            }
        })
    })
    const p2 = new Promise((resolve,reject)=>{
        setTimeout(() => {
            reject(new Error('错误'))
        },10);    //ajax在10秒前就会执行resolve()所以会正常输出请求的数据
    })
    const p = Promise.race([p1,p2])
        .then(data=>{
        console.log(data);
        })
        .catch(err=>{
            console.log(err);
        })

Alt text

改为10毫秒后,ajax请求能返回数据了,执行resove,所以会把返回值res传递给.then方法,打印出data。


用promise简单的封装一个类似于axios的方法

axios在客户端的实现就是基于原生的XMLHttpRequest()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 封装请求JSON数据的方法
        function getJsonData(url){
            // 返回一个promise
            return new Promise((resolve,reject)=>{
                // 创建一个XMLHttpRequest对象
                const http = new XMLHttpRequest()
                // 设置请求方法和请求路径
                http.open("GET",url)
                //设置返回数据类型
                http.responseType = 'json'
                // 设置请求头
                http.setRequestHeader("Accept","application/json")
                http.send()
                //监听状态的改变
                http.onreadystatechange = function(){
                 
                    if (this.readyState!==4){
                        return
                    }
                    // 状态为200说明请求成功
                    if (this.status===200){
                        resolve(this.response)
                    }else{
                        reject(new Error(this.statusText))
                    }
                }
            })
        }
        // 接下来就可以调用getData,因为getData返回值是一个promise,这个写法是否和axios有点类似
        getJsonData('data1.json').then((data)=>{
            console.log(data);
        }).catch(err=>{
            console.log(err);
        })