Promise

222 阅读11分钟

promise是同步还是异步的?

promise 本身是同步的, 执行的结果先打印出1再打印出2,如果 promise 是异步的应该先打印出 2,所以 promise 本身是同步

let oP = new Promise( (res, rej) => {
     console.log(1);
});
console.log(2);

image.png

promise 的回调 then 是异步的, 执行的结果1,2,3,因为then是异步的,所以先打印了2,最后再执行回调打印出3

let oP = new Promise((res, rej) => {
      console.log(1);
      res(3)
    });
    oP.then((res) => {
      console.log(res);
    });
    console.log(2);

image.png

一、promise

0. Promise三种 状态

Promise 是 ES6 提供的一种异步解决方案,比回调函数更加清晰明了。

Promise 有三种状态,分别是:1.等待中(pending)2.完成了 (resolved)3.拒绝了(rejected)

这个状态一旦从等待状态变成为其他状态就永远不能更改状态了,也就是说一旦状态变为 resolved 后,就不能再次改变

new Promise((resolve, reject) => {
  resolve('success')
  // 无效
  reject('reject')
})

当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的

new Promise((resolve, reject) => {
  console.log('new Promise')
  resolve('success')
})
console.log('finifsh')
// new Promise -> finifsh

Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,
并且是一个全新的 Promise,原因也是因为状态不可变。如果你在 then 中 使用了 return,
那么 return 的值会被 Promise.resolve() 包装

Promise.resolve(1)
  .then(res => {
    console.log(res) // => 1
    return 2 // 包装成 Promise.resolve(2)
  })
  .then(res => {
    console.log(res) // => 2
  })

当然了,Promise 也很好地解决了回调地狱的问题,例如:

ajax(url, () => {
    // 处理逻辑
    ajax(url1, () => {
        // 处理逻辑
        ajax(url2, () => {
            // 处理逻辑
        })
    })
})


可以改写成

ajax(url)
  .then(res => {
      console.log(res)
      return ajax(url1)
  }).then(res => {
      console.log(res)
      return ajax(url2)
  }).then(res => console.log(res))

1. 小用 Promise

<body>
    <button class="btn">抽奖</button>

    <script>
        function random(min, max) {
            return Math.floor(Math.random() * (max - min)) + min;
        }
        let btn = document.querySelector('.btn');
        btn.addEventListener('click', () => {
            let num = random(1, 100);
            const p = new Promise((resolve, reject) => {
                if (num < 30) {
                    resolve(num)
                } else {
                    reject(num);
                }
            })
            p.then((value) => {
                console.log(`中奖了,您的中奖号码是 ${num}`)
            }, (value) => {
                console.log(`再加油, 您的号码是 ${num}`)
            })
        })
    </script>
</body>

2. Promise 回调返回的是 Promise 对象

  • Promise 中 then 返回的 result 都是 Promise 对象

  • 如果 return 是 Promise 的 reject,是返回失败的状态的 Promise 对象

  • 其他的包含 return 的是基本数据类型或者引用类型,返回的都是成功状态的 Promise 对象。

<script>
    let p = new Promise((resolve, reject) =>{
        resolve('成功')
    })
    let result = p.then(res => {
        // return res
        // return undefined
        return new Promise((resolve, reject) => {
            // resolve('success')
            reject('fail')
        })
    })
    // console.log(result, 'res')
    // console.log(result, 'undefined')
    // console.log(result, 'success')
    console.log(result, 'fail')
</script>

3. Promse 多次进行回调是否执行

一个 Promise 指定多个成功/失败回调函数,当 Promise 改变对应状态时,多个成功/失败回调函数会被调用,不然不会被调用

let p = new Promise((resolve, reject) => {
    // 这里不改变状态, 就不会执行下面的then 回调函数。
    resolve('姓名:')
})
p.then(res => {
    console.log(res)
})
p.then(res => {
    console.log(`${res}xxl`)
})

4. 改变 Promise 状态的三种方法

Promise 有三种状态, pending, resolve, reject

改变 Promise 对象的状态有 种:第1种,【调用 resolve】; 第2种:【调用 reject】; 第3种,【抛出错误

let p = new Promise((resolve, reject) =>{
    // resolve('成功')
    // reject('失败')
    throw('错了')
})
p.catch(reson => {
    console.log(reson, 'reson')
})
console.log(p, 'p')

5. 中断 Promise 链

如何中断 Promise 链?

当使用 promise 的 then 链式调用时,要在中间中断,不再调用后面的回调函数

办法: 在回调函数中返回一个 pending 状态的 promise 对象 return new Promise(() => {})

let p = new Promise((resolve, reject) => {
    resolve('success')
})
p.then((res => {
    console.log(111)
    return new Promise((resolve, reject) => {
        resolve('成功')
    })
})).then(res => {
    console.log(222)
    // 回调函数中返回一个 pending 状态的 promise 对象
    return new Promise(() => {})
}).then(res => {
    console.log(333)
})

6. Promise 异常穿透 catch

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

let p = new Promise((resolve, reject) => {
    resolve('成功')
    // reject('错了')
})

p.then(res => {
    console.log(res, 1)
    return new Promise((resolve, reject) => {
        resolve('success')
    })
}).then(res => {
    console.log(res, 2)
}).then(res => {
    throw '错啦'
    console.log(res, 3)
}).catch(reson => {
    console.log(reson, 'reson')
})

第一种情况:Promise 内部抛出异常, 通过 then 的第二个函数 err 来捕捉异常

// Promise 的异常捕获问题
const promise = new Promise((resolve,reject) => {
    throw new Error('test')
})

// 
promise.then(res => {
    console.log(res);
},err => {
    // 通过then的第二个函数来进行捕捉
    console.log(err); // [Error test]
})

第二种情况:Promise.then 的第一个函数出现了异常, 通过catch 来捕捉 Promise.then 的第一个函数。

const promise = new Promise((resolve,reject) => {
    resolve(666)
})

// promise.then 通过.catch 进行捕捉
promise.then(res => {
    throw new Error('test2')
},err => {
    console.log(err); 
}).catch(err => {
    console.log(err);  //[Error test2]
})

7. then 方法异步执行的回调函数顺序

下面的输出顺序为 111, 333, 222 。

js 从上到下执行, new promise 是微任务,先执行,接下来是同步 console.log(333), then 中是异步执行的回调函数,最后执行

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

p.then(v => {
    console.log(2222)
})

console.log(333)

8. Promise 链式调用 then 后面接着跟 then

  • Promise 对象 then 只要有 return (返回值),都是返回一个 Promise 对象,后面紧跟着链式调用的值都是有值的;
image.png
  • 如果 then 中没有 return(返回值),后面紧跟着链式调用的值为 undefined
image.png

9. resolve 方法

  • Promise.resolve 如果传参是一个非 Promise 对象,则返回结果为一个成功的 Promise 对象
let p1 = Promise.resolve('xxl');
console.log(p1)  

// 结果:
Promise {<resolved>: "xxl"}
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: "xxl" 
  • Promise.resolve 如果传参是一个 Promise 对象,则 这个 Promise 对象的参数返回结果决定 P2是成功的 Promise 对象还是一个失败的 Promise对象
let p2 = Promise.resolve(new Promise((resolve, reject) => {
    resolve('成功')
}))
console.log(p2)   // p2 为成功的 Promise 对象

let p3 = Promise.resolve(new Promise((resolve, reject) => {
    reject('失败')
}))

// 防止p3 作为一个失败的 Promsie报错,进行 catch 
p3.catch(res => {})
console.log(p3') // p3 为失败的 Promise 对象

10. reject 方法

Promise.reject 无论传入什么参数包括返回成功的 promsie 对象,返回 整体就是一个失败的 Promise 对象。

let p1 = Promise.reject('xxl');
console.log(p1)

let p2 = Promise.reject(new Promise((resolve, reject) =>{
    resolve('成功')
}))
p2.catch(res => {
    console.log(res, 'res')
})
console.log(p2)

11. all 方法

Promise.all 传参 参数为由 Promise 对象组成的数组

如果 其中一个Promise 对象返回的是失败,则 整体 是直接返回这一个失败的 Promise 对象,其他的后续就不管了

如果 参数里 Promise 对象全部返回的是成功的 Promise 对象,则 整体 是返回一个成功的 Promise 对象

let p1 = new Promise((resolve, reject) => {
    resolve('成功')
})
let p2 = Promise.resolve('success');
let p3 = Promise.resolve('o Year');
let pa = Promise.reject('失败了a');
let pb = Promise.reject('失败了b');

let pSuccess = Promise.all([p1, p2, p3])
let pFail = Promise.all([p1, pa, pb, p3])

pSuccess.then((res) => {
    console.log(res)       // ["成功", "success", "o Year"]
    console.log(res[0])    // 成功
})
pFail.catch((reson) => {
    console.log(reson)     // 失败了
})

12. race 方法

Promise.race 接收 一个由 Promise 对象组成的参数

Promise.race 参数中第一个返回成功的 Promise 对象作为返回的结果。

let p1 = new Promise((resolve, reject) => {
    resolve('成功')
})
let p2 = Promise.resolve('success');
let p3 = Promise.resolve('O Year');
let p4 = Promise.reject('fail 了');

const pSuccess = Promise.race([p2, p1, p3]);
pSuccess.then(res => {
    console.log(res)    // success
})

const pFail = Promise.race([p4]);
pFail.catch((e) => {
    console.log(e)      // fail 了
})

13. async 函数

async 函数 特点:

  1. async 函数的 返回值为 Promise 对象, promise 对象的结果由 async 函数执行的返回值决定
  2. 如果 async 函数 返回的是一个非 promise 值,则返回一个成功的promise 对象
  3. async 函数 和 Promise 中的 then 方法返回规则一致

async 函数的 返回值为 Promise 对象

async function box() {
   return 'xxl'
}
let result = box();
console.log(result)

// 结果:
Promise {<resolved>: "xxl"}
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: "xxl"

如果 async 函数返回的是一个非 promise 值,则返回一个成功的promise 对象 async 函数 和 Promise 中的 then 方法返回规则一致

async function box() {
    return 12
}

let result = box();
result.then(res => {
    console.log(res)   // 12
})

promise 对象的结果由 async 函数执行的返回值决定

async function box () {
    return new Promise((resolve, rejejct) => {
        throw '错误啦'
    })
}

let result = box();
result.catch(err =>{
    console.log(err)  // 错误啦
})

14. await 函数

await 表达式:

  1. await 右侧的表达式一般为 Promise 对象,但也可以是其他值
  2. 如果 表达式是 promise 对象,await 返回的是 promise 成功的值。如果表达是其他值,直接将此值作为 await 的返回值
  3. await 必须写在 async 函数中,但 async 函数中可以没有 await
  4. 如果 await 的 promise 失败了,就会抛出异常,需要通过 try...catch 捕获处理
async function box() {
    // 右侧跟着非 Promise对象,直接返回值
    // let res  = await null; 
    // console.log(res)            // null

    
    let p = new Promise((resolve, reject) => {
        resolve('1111')
        // reject('333')
        // throw '444'
    })
    
    try {
        // await 右侧跟着 promise 对象
        let res = await p; 
        console.log(res, 'res')
    } catch (e) {
        console.log(e)
    }
}

box()

15. async 与 await 结合使用

利用 async 函数 结合 await 来获取各个文件的内容

let fs = require('fs');
let util = require('util');
// util.promisify 可以把对象转化成 promise 对象
let mineReadFile = util.promisify(fs.readFile);

// 利用回调函数进行无限嵌套来获取每个文件的内容
// fs.readFile(('./resouce/1.html'), (err, data1) => {
//     fs.readFile(('./resouce/2.html'), (err, data2) => {
//         fs.readFile(('./resouce/3.html'), (err, data3) => {
//             console.log(data1.toString() + data2.toString() + data3.toString())
//         })
//     })
// })


// 利用 async 函数 结合 await 来获取各个文件的内容
async function box() {
   // 用 try...catch来进行错误捕获
   try {
       let data1 = await mineReadFile('./resouce/1.html');
       let data2 = await mineReadFile('./resouce/2.html');
       let data3 = await mineReadFile('./resouce/3.html');
       console.log(data1.toString() + data2.toString() + data3.toString())
   } catch (e) {
       console.log(e.code)
   }
}
box()

获取文件内容如下

image.png

15. 能用 return 代替 resolve 吗

不能, 通过 return 无法改变 promise 的状态,无法改变状态,也就无法进行 then 链式调用了

let p  = new Promise((resolve, reject) => {
    return ('123')
})
p.then(res => {
    console.log(res, '---')  // 这里不会进行输出
})

16. resolve 后面的 代码还会执行吗?

会执行

let p  = new Promise((resolve, reject) => {
    resolve('456')
    console.log('0000')
})
p.then(res => {
    console.log(res)
})

// 结果:
000
456

16.自己手动写 Promise

index.html

<body>
    <script>
        
        let p = new Promise((resolve, reject) => {
            /**
             * Promsie 同步任务:指的是直接在这块调用 resolve() 或 reject() 或 throw 抛出错误
             * Promise 异步任务:指的是在这里进行 数据请求,或 setTimeout 等等。
             */
            // 异步任务回调的执行,这里用 setTimeout 来模拟接口请求
            // setTimeout(() => {
            //     // reject('fail')
            // }, 1000);
        })
        // p.then(value =>{
        //     console.log(111)
        // }).then(value =>{
        //     console.log(222)

        // }).then(value => {
        //     console.log(333)

        // }).catch(reason =>{
        //     console.log(reason, 'reason')
        // })





        // 自定义封装 Promise.resolve方法
        // let p2 = Promise.resolve('ok');
        // let p2 = Promise.resolve(new Promise((resolve, reject) => {resolve('222s')}));
        // let p2 = Promise.resolve(Promise.resolve('333'));
        // console.log(p2)


        // 自定义封装 Promsie.reject 方法
        // let p2 = Promise.reject('fail');
        // let p3 = Promise.reject(new Promise((resolve, reject) => {
        //     resolve('333')
        // }));
        // let p4 = Promise.reject(Promise.reject('444'))
        // console.log(p4)


        // 自定义封装 Promise.all 方法
        // let p2 = Promise.resolve('ok');
        // let p3 = Promise.reject('fail');
        // let p4 = Promise.resolve('year');
        // let result = Promise.all([p2, p3, p4]);
        // console.log(result, 'result')


        // 自定义封装 Promise.race 方法
        // let p2 = Promise.resolve('Ok');
        // let p3 = Promise.reject('fail');
        // let p4 = Promise.resolve('success');
        // let result = Promise.race([p2, p3, p4]);
        // console.log(result)



        // 自定义封装 then 方法异步回调函数的执行
        // let p2= new Promise((resolve, reject) =>{
        //     resolve('ok');
        //     console.log(111)
        // })
        // p2.then(v => {
        //     console.log(2222)
        // })
        // console.log(333)




        // 自定义 class 类封装
        let p2 = Promise.resolve('333');
        console.log(p2)
    </script>
</body>

自己手动封装的 classPromise.js

class Promise {
    constructor(executor) {
        // 设置属性
        this.PromiseState = 'pending';          // 设置 Promise 初始状态为 pending
        this.PromiseResult = null;              // 设置 Promise 返回结果为 null;
        this.callBacks = [];                    // 默认异步的回调对象为空,指定多个回调的实现用数组形式
        // console.log(this, 'this')            // 这里的 this 指下 Promise 构造函数
        const _this = this;                       // 保存 this 构造函数的作用域

        // resolve函数
        function resolve(data) {
            // console.log(this, '_this')              // 这里的 this 指向 window, 需由上面进行保存下

            // 确保 promise状态只能改变一次,通过判断当前Promise 的状态
            if (_this.PromiseState !== 'pending') return;
            _this.PromiseState = 'fulfilled';       // 设置 Promise 状态为成功状态
            _this.PromiseResult = data;             // 设置 Promise 输出结果为 data
            // 为了让 then 方法中的函数进行异步执行,通过用  setTimeout 让它加入队列
            setTimeout(() => {
                // 在状态改变后进行成功的回调
                _this.callBacks.forEach(item => {
                    // 执行成功的回调函数
                    item.onResolve(data)
                })
            });

        }
        // reject 函数
        function reject(data) {
            // 确保 promise状态只能改变一次,通过判断当前Promise 的状态
            if (_this.PromiseState !== 'pending') return;
            _this.PromiseState = 'rejected';       // 设置 Promise 状态为失败状态
            _this.PromiseResult = data;             // 设置 Promise 输出结果为 data
            // 为了让 then 方法中的函数进行异步执行,通过用  setTimeout 让它加入队列
            setTimeout(() => {
                // 在状态改变后进行成功的回调
                _this.callBacks.forEach(item => {
                    // 执行失败的回调函数
                    item.onReject(data)
                })
            });
        }
        // 封装自定义抛出错误
        try {
            executor(resolve, reject)
        } catch (e) {
            // 失败去执行 reject 函数
            reject(e)
        }
    }


    // 添加 then 方法
    then(onResolve, onReject) {
        // 为了异常穿透与值进行传递
        if (typeof onResolve !== 'function') {
            onResolve = reason => reason;
        }
        // 为了异常穿透与值进行传递
        if (typeof onReject !== 'function') {
            onReject = reason => {
                throw reason
            };
        }
        const _this = this;
        return new Promise((resolve, reject) => {

            function callBack(type) {
                try {
                    let result = type(_this.PromiseResult);
                    if (result instanceof Promise) {
                        result.then(v => {
                            resolve(v)
                        }, r => {
                            reject(r)
                        })
                    } else {
                        // 结果的对象状态为【成功】
                        resolve(result)
                    }
                } catch (e) {
                    reject(e)
                }
            }
            // 成功执行该回调
            if (this.PromiseState === 'fulfilled') {
                // 为了让 then 方法中的函数进行异步执行,通过用  setTimeout 让它加入队列
                setTimeout(() => {
                    callBack(onResolve);
                });
            }
            // 失败执行该回调
            if (this.PromiseState === 'rejected') {
                // 为了让 then 方法中的函数进行异步执行,通过用  setTimeout 让它加入队列
                setTimeout(() => {
                    callBack(onReject);
                });

            }
            // 异步任务在pending的时候保存成功和失败的回调
            if (this.PromiseState === 'pending') {
                // 保存回调函数,方便Promise异步操作的时候,执行对应的成功或失败的回调函数
                this.callBacks.push({
                    // 保存成功的回调函数
                    onResolve: function () {
                        callBack(onResolve);
                    },
                    // 保存失败的回调函数           
                    onReject: function () {
                        callBack(onReject);
                    },
                })
            }
        })
    }

    // 添加 catch 方法
    catch(onReject) {
        return this.then(undefined, onReject)
    }


    // 添加 resolve 方法, 这里的 resolve 不属于实例对象,属性类,所以我们这里用 static 这个关键字对它进行描述, 表明resolve这里是一个静态成员
    static resolve(value) {
        return new Promise((resolve, reject) => {
            if (value instanceof Promise) {
                value.then(v => {
                    resolve(v)
                }, r => {
                    reject(r)
                })
            } else {
                resolve(value)
            }

        })
    }

    // 添加 reject 方法
    static reject(value) {
        return new Promise((resolve, reject) => {
            reject(value)
        })
    }

    /**
     * 添加 all 方法,参数为 由一个promise对象组成的数组
     * 如果 promise 对象组成的数组都是返回成功的状态,则整体返回是一个成功的状态的 Promise 对象
     *  如果 promise 对象组成的数组有一个是返回失败的状态,则整体返回是一个失败的状态的 Promise 对象
     */
    static all(promises) {
        let arr = [];       // 所有promise 返回结果的集合
        let count = 0;
        // 返回一个 Promise 对象
        return new Promise((resolve, reject) => {
            for (var i = 0; i < promises.length; i++) {
                promises[i].then(v => {
                    arr[i] = v;
                    count++;
                    if (count == promises.length) {
                        resolve(arr)
                    }
                }, r => {
                    reject(r)
                })
            }
        })
    }

    /**
     * 添加 race 方法
     * 谁先返回就用谁的状态
     */
    static race(promises) {
        return new Promise((resolve, reject) => {
            for (var i = 0; i < promises.length; i++) {
                promises[i].then(v => {
                    // 修改返回对象的状态为【成功】
                    resolve(v)
                }, r => {
                    // 修改返回对象的状态为【失败】
                    reject(r)
                })
            }
        })
    }
}

二、async 与 promise 的区别

async/await 是基于promise实现的,他不能用于普通的回调函数
async/await 使得异步代码看起来像同步代码
async/await 与 Promise 一样,是非阻塞的。

不同

async关键字。await 关键字只能用在 async 定义的函数内。async 函数会引式返回一个 promise,改 promise 的 resolve 值就是函数 return 的值。

简洁:使用 async 和 await 明显节约了不少代码,不需要.then,不需要写匿名函数处理 promise 的 resolve 的值,不需要定义多余的 data 变量,还避免了嵌套代码。

async/await让 try/catch 可以同时处理同步和异步错误。try/catch 不能处理JSON.parse的错误,因为他在promise中。此时需要.catch,这样的错误处理代码非常冗余。并且,在我们的实际生产代码会更加复杂