3

js--promise、async 和 await 相关知识总结 - 丶Serendipity丶

 1 year ago
source link: https://www.cnblogs.com/zaishiyu/p/14929463.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

2182006-20220509170238221-377645647.jpg

  promise 是前端开发人员必须掌握的知识点,本文来总结一下相关学习笔记。

  1、什么是prommise,promise 解决了什么问题

  a、promise 是什么

  Promise 是承诺的意思,承诺它过一段时间会给你一个结果。Promise 是一种解决异步编程的方案,相比回调函数和事件更合理和更强大。从语法上讲,promise 是一个对象,从它可以获取异步操作的消息;

  promise 有三种状态:pending 初始状态也叫等待状态,fulfiled成功状态,rejected 失败状态;状态一旦改变,就不会再变。创造 promise实例后,它会立即执行。需要注意,promise 的状态是不可逆的,一旦状态由 pending 变为 fulfiled 或者reject 状态,意味着已经产生了结果,同样,转为成功状态会有成功的结果,转为失败状态会返回失败的原因。

  promise 作为构造函数,接收两个参数,分别是成功和失败的回调函数。

  b、promise 解决了什么问题

  我们先来看如下代码,并不陌生

  setTimeout(function () {
    console.log("开始执行");
  }, 3000);

  上面的代码,粗略的可以认为在 3 秒后,程序输出开始执行,但是如果业务比较复杂,我们想在3秒后输出开始执行,再隔 3 秒打印一次第二次执行呢?接着在隔 3 秒打印第三次执行,代码会这样写:

  setTimeout(function () {
    console.log("开始执行");
    setTimeout(function () {
      console.log("第二次执行");
      setTimeout(function () {
        console.log("第三次执行");
      }, 3000);
    }, 3000);
  }, 3000);

  再看上面的代码,如果后面的需求再次优化,需要类似的打印第 4,5,6 次呢?我们的代码还是这样一层层嵌套起来吗? 这样多层函数之间互相嵌套,就产生了回调地狱的问题,这样写代码有个很大的缺点:(1)代码耦合行太强,牵一发而动全身,可维护性很差,同样,大量冗余的代码互相嵌套,可读性很差。因此,为了解决回调地狱的问题,ES6 提出了 Promise。通过 promise 将上面的代码改装一下将显的代码优雅很多:

    function sleep(second) {
      return new Promise((resolve, reject) => {
        setTimeout(() => resolve(), second * 1000);
      });
    }
    sleep(3)
      .then(() => {
        console.log("开始执行");
        return sleep(3);
      })
      .then(() => {
        console.log("第二次执行");
        return sleep(3);
      })
      .then(() => {
        console.log("第三次执行");
      });

  2、ES6 中 promise 的使用

  1)then 链式调用

  从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise 的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递 callback 函数要简单、灵活的多。所以使用 Promise 的正确场景是这样的:

    p.then((data) => {
      console.log(data);
    })
      .then((data) => {
        console.log(data);
      })
      .then((data) => {
        console.log(data);
      });

  then 是实例状态发生改变时的回调函数,第一个参数是 resolved 状态的回调函数,第二个参数是 rejected 状态的回调函数,then方法返回的是一个新的Promise实例,也就是promise能链式书写的原因。默认常写第一个参数即可,reject 状态的回调可以通过 catch 来捕获异常。

  2)catch 方法用来指定 promise 实例状态变为 rejected 的捕获

  catch 捕获reject状态的回调,相当于 then中的第二个参数,一般写法如下:

    p.then((data) => {
      console.log("resolved", data);
    }).catch((err) => {
      console.log("rejected", err);
    });

  也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。

  3)all 方法将多个 promise 实例包装成一个新的 promise 实例(谁跑的慢,以谁为准执行回调)

  Promise.all 方法接收一个数组(可迭代对象)作为参数,并且数组中的每个元素都是 Promise 实例,最终返回结果也为一个 Promise  对象,例如:

const p = Promise.all([p1, p2, p3]),实例p的状态由p1、p2、p3决定,分为两种:

  只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数;

  只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数;

  常见的写法如下:

    let Promise1 = new Promise(function (resolve, reject) {});
    let Promise2 = new Promise(function (resolve, reject) {});
    let Promise3 = new Promise(function (resolve, reject) {});
    
    let p = Promise.all([Promise1, Promise2, Promise3]);
    
    p.then(
      (res) => {
        // 三者都成功则成功,成功后处理
      },
      (err) => {
        // 三者只要有失败的就返回失败,失败后处理
      }
    );

  有了all 方法,我们就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据,比如开发中打开网页时,预先加载需要用到的各种资源如图片、flash 以及各种静态文件,所有的都加载完后,我们再进行页面的初始化。

  (4)race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例(谁跑的快,以谁为准执行回调)

  const p = Promise.race([p1, p2, p3]);只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变,率先改变的 Promise 实例的返回值则传递给p的回调函数。

  使用场景:比如,我们在页面加载的时候,需要请求后端获取某个图片的URL,这里我们可以设置请求的超时时间,当在设定的时间内后端接口没有返回时,页面给出请求超时提示。代码如下:

    //请求某个图片资源
        function requestImg() {
            var p = new Promise((resolve, reject) => {
                var img = new Image();
                img.onload = function () {
                    resolve(img);
                }
                img.src = '图片的路径';
            });
            return p;
        }
        //延时函数,用于给请求计时
        function timeout() {
            var p = new Promise((resolve, reject) => {
                setTimeout(() => {
                    reject('图片请求超时');
                }, 5000);
            });
            return p;
        }
        Promise.race([requestImg(), timeout()]).then((data) => {
            console.log(data);
        }).catch((err) => {
            console.log(err);
        });

  3、promise 的缺点

  1)无法取消 Promise,一旦新建它就会立即执行,无法中途取消  

  2)如果不设置回调函数,Promise 内部抛出的错误,不会反映到外部

  3)当处于 pending(等待)状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成

  4、手动实现 Promise

        function resolvePromise(promise2, x, resolve, reject) {
            //判断x是不是promise
            //规范中规定:我们允许别人乱写,这个代码可以实现我们的promise和别人的promise 进行交互
            if (promise2 === x) {//不能自己等待自己完成
                return reject(new TypeError('循环引用'));
            };
            // x是除了null以外的对象或者函数
            if (x != null && (typeof x === 'object' || typeof x === 'function')) {
                let called;//防止成功后调用失败
                try {//防止取then是出现异常  object.defineProperty
                    let then = x.then;//取x的then方法 {then:{}}
                    if (typeof then === 'function') {//如果then是函数就认为他是promise
                        //call第一个参数是this,后面的是成功的回调和失败的回调
                        then.call(x, y => {//如果Y是promise就继续递归promise
                            if (called) return;
                            called = true;
                            resolvePromise(promise2, y, resolve, reject)
                        }, r => { //只要失败了就失败了
                            if (called) return;
                            called = true;
                            reject(r);
                        });
                    } else {//then是一个普通对象,就直接成功即可
                        resolve(x);
                    }
                } catch (e) {
                    if (called) return;
                    called = true;
                    reject(e)
                }
            } else {//x = 123 x就是一个普通值 作为下个then成功的参数
                resolve(x)
            }

        }

        class Promise {
            constructor(executor) {
                //默认状态是等待状态
                this.status = 'panding';
                this.value = undefined;
                this.reason = undefined;
                //存放成功的回调
                this.onResolvedCallbacks = [];
                //存放失败的回调
                this.onRejectedCallbacks = [];
                let resolve = (data) => {//this指的是实例
                    if (this.status === 'pending') {
                        this.value = data;
                        this.status = "resolved";
                        this.onResolvedCallbacks.forEach(fn => fn());
                    }

                }
                let reject = (reason) => {
                    if (this.status === 'pending') {
                        this.reason = reason;
                        this.status = 'rejected';
                        this.onRejectedCallbacks.forEach(fn => fn());
                    }
                }
                try {//执行时可能会发生异常
                    executor(resolve, reject);
                } catch (e) {
                    reject(e);//promise失败了
                }

            }
            then(onFuiFilled, onRejected) {
                //防止值得穿透 
                onFuiFilled = typeof onFuiFilled === 'function' ? onFuiFilled : y => y;
                onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; }
                let promise2;//作为下一次then方法的promise
                if (this.status === 'resolved') {
                    promise2 = new Promise((resolve, reject) => {
                        setTimeout(() => {
                            try {
                                //成功的逻辑 失败的逻辑
                                let x = onFuiFilled(this.value);
                                //看x是不是promise 如果是promise取他的结果 作为promise2成功的的结果
                                //如果返回一个普通值,作为promise2成功的结果
                                //resolvePromise可以解析x和promise2之间的关系
                                //在resolvePromise中传入四个参数,第一个是返回的promise,第二个是返回的结果,第三个和第四个分别是resolve()和reject()的方法。
                                resolvePromise(promise2, x, resolve, reject)
                            } catch (e) {
                                reject(e);
                            }
                        }, 0)
                    });
                }
                if (this.status === 'rejected') {
                    promise2 = new Promise((resolve, reject) => {
                        setTimeout(() => {
                            try {
                                let x = onRejected(this.reason);
                                //在resolvePromise中传入四个参数,第一个是返回的promise,第二个是返回的结果,第三个和第四个分别是resolve()和reject()的方法。
                                resolvePromise(promise2, x, resolve, reject)
                            } catch (e) {
                                reject(e);
                            }
                        }, 0)

                    });
                }
                //当前既没有完成也没有失败
                if (this.status === 'pending') {
                    promise2 = new Promise((resolve, reject) => {
                        //把成功的函数一个个存放到成功回调函数数组中
                        this.onResolvedCallbacks.push(() => {
                            setTimeout(() => {
                                try {
                                    let x = onFuiFilled(this.value);
                                    resolvePromise(promise2, x, resolve, reject);
                                } catch (e) {
                                    reject(e);
                                }
                            }, 0)
                        });
                        //把失败的函数一个个存放到失败回调函数数组中
                        this.onRejectedCallbacks.push(() => {
                            setTimeout(() => {
                                try {
                                    let x = onRejected(this.reason);
                                    resolvePromise(promise2, x, resolve, reject)
                                } catch (e) {
                                    reject(e)
                                }
                            }, 0)
                        })
                    })
                }
                return promise2;//调用then后返回一个新的promise
            }
            catch(onRejected) {
                // catch 方法就是then方法没有成功的简写
                return this.then(null, onRejected);
            }
        }
        Promise.all = function (promises) {
            //promises是一个promise的数组
            return new Promise(function (resolve, reject) {
                let arr = []; //arr是最终返回值的结果
                let i = 0; // 表示成功了多少次
                function processData(index, data) {
                    arr[index] = data;
                    if (++i === promises.length) {
                        resolve(arr);
                    }
                }
                for (let i = 0; i < promises.length; i++) {
                    promises[i].then(function (data) {
                        processData(i, data)
                    }, reject)
                }
            })
        }
        // 只要有一个promise成功了 就算成功。如果第一个失败了就失败了
        Promise.race = function (promises) {
            return new Promise((resolve, reject) => {
                for (var i = 0; i < promises.length; i++) {
                    promises[i].then(resolve, reject)
                }
            })
        }
        // 生成一个成功的promise
        Promise.resolve = function (value) {
            return new Promise((resolve, reject) => resolve(value);
        }
        // 生成一个失败的promise
        Promise.reject = function (reason) {
            return new Promise((resolve, reject) => reject(reason));
        }
        module.exports = Promise;

  5、async 和 await相关

  async/await 是 ES7 提出的基于 Promise 的解决异步的最终方案。

  async 就是 generation 和 promise 的语法糖,async 就是将 generator的*换成 async,将 yiled 换成 await函数前必须加一个 async,异步操作方法前加一个 await 关键字,意思就是等一下,执行完了再继续走,注意:await 只能在 async 函数中运行,否则会报错,Promise 如果返回的是一个错误的结果,如果没有做异常处理,就会报错,所以用 try..catch 捕获一下异常就可以了。

  async

async是一个加在函数前的修饰符,被async定义的函数会默认返回一个Promise对象resolve的值。因此对async函数可以直接then,返回值就是then方法传入的函数。使用如下:

        async function fun() {
            console.log(1);
            return 1;
        }
        fun().then(val => {
            console.log(val) // 1,1
        })

  await

  await 也是一个修饰符,只能放在async定义的函数内。可以理解为等待。await 修饰的如果是Promise对象:可以获取Promise中返回的内容(resolve或reject的参数),且取到值后语句才会往下执行;如果不是Promise对象:把这个非promise的东西当做await表达式的结果。使用如下:

        async function fun() {
            let a = await new Promise((resolve, reject) => {
                setTimeout(function () {
                    resolve('setTimeout promise')
                }, 3000)
            })
            let b = await "表达式";
            let c = await function () {
                return '函数表达式'
            }()
            console.log(a, b, c)
        }
        fun(); // 3秒后输出:"setTimeout promise" "表达式" "函数表达式"

   以上就是本文的全部内容,希望给读者带来些许的帮助和进步,方便的话点个关注,小白的成长之路会持续更新一些工作中常见的问题和技术点。

2182006-20210522104151513-284237194.png

谁跑的慢,以谁为准执行回调


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK