Promise基本用法

发布时间 2023-04-08 16:24:48作者: XZGNVPY

JavaScript它的执行环境是单线程的,单线程就是任务只能一个一个的完成,这个任务完成之后才能执行下一个,它会阻塞其它任务。

而异步模式可以一起执行多个任务。常见的异步模式有定时器,接口调用和事件函数,Promise就是接口调用里面的一种方式,它是es6提供的一种异步解决方案。简单来说的话,Promis就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作的结果)。

JavaScript中常见的接口调用有

Ajex,fetch,promise,axios,async/await。

多次异步调用的依赖分析:

1.多次异步调用的结果,顺序可能不同步。

2.异步调用的结果如果存在依赖,则需要嵌套。

3.在ES5中,当进行多层嵌套回调时,会导致代码层次过多,很难进行维护和二次开发;而且会导致回调地狱的问题。ES6中的Promise 就可以解决这两个问题。

Promise介绍

Promis是异步编程的一种方案。从语法上来讲,Promise是一个对象,它可以获取异步操作的消息。

Promise的优点:

Promise对象,可以将异步操作以同步的流程表达出来,可以很好地解决回调的问题,避免层层嵌套的回调函数。语法非常简洁,Promise对象提供了简洁的API,使得控制异步操作更加容易。

Promise的基本用法

Es6规定Promise对象是一个构造函数,用来生成Promise实例

(1)使用new实例化一个Promise对象,Promise的构造函数中传递一个参数。这个参数是一个函数,改函数用来处理异步任务。

(2)并且会传入两个参数:reslove和reject,分别表示异步执行成功后的回调函数和异步执行失败后的回调函数;

(3)通过Promis.then()处理返回结果

在浏览器控制台上的输出如下

这里用timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(100ms)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。

这里要知道Promise对象的三个状态

初始化状态(等待状态):pending

成功状态:fullfilled

失败状态:rejected

(1)当new Promise()执行之后,promise对象的状态会被初始化为pending,这个状态是初始化状态。new Promise()这行代码,括号里的内容是同步执行的。括号里定义一个function,function有两个参数:resolve和reject。如下:

如果请求成功了,则执行resolve(),此时,promise的状态会被自动修改为fullfilled。

如果请求失败了,则执行reject(),此时,promise的状态会被自动修改为rejected

(2)promise.then()方法,括号里面有两个参数,分别代表两个函数 function1 和 function2:

如果promise的状态为fullfilled(意思是:如果请求成功),则执行function1里的内容

如果promise的状态为rejected(意思是,如果请求失败),则执行function2里的内容

这里有一段代码

let promise = new Promise(function(resolve, reject) {

  console.log('Promise');

  resolve();

});

promise.then(function() {

  console.log('resolved.');

});

console.log('Hi!');

输出顺序是Promise,Hi!,resolved

上面代码中,Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。

这里简单说一下JavaScript中的宏任务和微任务

宏任务有Event Table、Event Queue,微任务有Event Queue

1.宏任务:包括整体代码script,setTimeout,setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境);

2.微任务:Promise、MutaionObserver、process.nextTick(Node.js 环境)

注:new Promise中的代码会立即执行,then函数分发到微任务队列,process.nextTick分发到微任务队列Event Queue

任务进入执行栈----同步任务还是异步任务----同步的进入主线程,异步的进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入Event Queue。主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。上述过程会不断重复,也就是常说的Event Loop(事件循环)。

这里有一个简单地宏任务微任务用例

setTimeout(function() {

    console.log('宏任务setTimeout');  //先遇到setTimeout,将其回调函数注册后分发到宏任务Event Queue

  //如果setTimeout设置时间,那它会先把函数放到宏任务Event Table,等时间到了再放入宏任务Event Queue里面

})

new Promise(function(resolve) {

    console.log('微任务promise');     //new Promise函数立即执行

    resolve();                     //必须resolve执行才能执行then

}).then(function() {

    console.log('微任务then');          //then函数分发到微任务Event Queue

})

console.log('主线程console');

//执行顺序结果: 微任务promise、主线程console、微任务then、宏任务setTimeout

Promise 的回调函数不是正常的异步任务,而是微任务(microtask)。它们的区别在于,正常任务追加到下一轮事件循环,微任务追加到本轮事件循环。这意味着,微任务的执行时间一定早于正常任务。并且如果有多个Pomise的回调函数进入微任务队列,那么这些微任务会一起执行完才会去执行排队的宏任务。

事件循环,宏任务,微任务的关系如图所示

另外,resolve()和reject()这两个方法,是可以给promise.then()传递参数的。

案例如下:

在浏览器控制台输出结果为

基于Promise处理多次ajex请求(链式调用)

有了 promise之后,我们可以把多层嵌套调用按照线性的方式进行书写,非常优雅。

也就是说:Promise 可以把原本的多层嵌套调用改进为链式调用。

代码如下:

<script type="text/javascript">

function queryData(url) {

        var promise = new Promise((resolve, reject) => {

          var xhr = new XMLHttpRequest();

          xhr.onreadystatechange = function () {

            if (xhr.readyState != 4) return;

            if (xhr.readyState == 4 && xhr.status == 200) {

              // 处理正常情况

              resolve(xhr.responseText); // xhr.responseText 是从接口拿到的数据

            } else {

              // 处理异常情况

              reject("接口请求失败");

            }

          };

          xhr.responseType = "text"; // 设置返回的数据类型

          xhr.open("get", url);

          xhr.send(null); // 请求接口

        });

        return promise;

      }

      // 发送多个ajax请求并且保证顺序

      queryData("https://jsonplaceholder.typicode.com/posts")

        .then(

          (data1) => {

            console.log(JSON.stringify(data1));

            // 请求完接口1后,继续请求接口2

            return queryData("https://jsonplaceholder.typicode.com/posts");

          },

          (error1) => {

            console.log(error1);

          }

        )

        .then(

          (data2) => {

            console.log(JSON.stringify(data2));

            // 请求完接口2后,继续请求接口3

            return queryData("https://jsonplaceholder.typicode.com/posts");

          },

          (error2) => {

            console.log(error2);

          }

        )

        .then(

          (data3) => {

            // 获取接口3返回的数据

            console.log(JSON.stringify(data3));

          },

          (error3) => {

            console.log(error3);

          }

        );

</script>

这里的url我用的是jsonplaceholder上面的数据,在运行的时候xhr.responseType = "text";这一行代码原本是xhr.responseType = "json";因为我的浏览器不能正常读取json文件,所以讲json改成了text这样就能正常读取了,如果改了之后的代码无法正常运行或者读取不出文件,可以尝试把代码改回json。

可以清楚的看到这里数据是被请求了3次,做了三次输出

Return函数的返回值

Return后面的返回值有两种情况

第一种:返回一个新的Promise实例对象。返回的实例对象会调用下一个.then()

这里我引用的例子和上面的处理ajex请求基本一样

<script type="text/javascript">

            /*

              基于Promise发送Ajax请求

            */

            function queryData(url) {

                return new Promise((resolve, reject) => {

                    var xhr = new XMLHttpRequest();

                    xhr.onreadystatechange = function() {

                        if (xhr.readyState != 4) return;

                        if (xhr.readyState == 4 && xhr.status == 200) {

                            // 处理正常情况

                            resolve(xhr.responseText);

                        } else {

                            // 处理异常情况

                            reject('接口请求失败');

                        }

                    };

                    xhr.responseType = 'text'; // 设置返回的数据类型

                    xhr.open('get', url);

                    xhr.send(null); // 请求接口

                });

            }

            // 发送多个ajax请求并且保证顺序

            queryData(' https://jsonplaceholder.typicode.com/posts')

                .then(

                    data1 => {

                        console.log(JSON.stringify(data1));

                        return queryData(' https://jsonplaceholder.typicode.com/posts ');

                    },

                    error1 => {

                        console.log(error1);

                    }

                )

                .then(

                    data2 => {

                        console.log(JSON.stringify(data2));

                        // 这里的 return,返回的是 Promise 实例对象

                        return new Promise((resolve, reject) => {

                            resolve('qianguyihao');

                        });

                    },

                    error2 => {

                        console.log(error2);

                    }

                )

                .then(data3 => {

                    console.log(data3);

                });

        </script>

第二种:返回普通值,返回的普通值会直接传递给下一个then,通过then参数中的函数的参数接收该值

<script type="text/javascript">

            /*

              基于Promise发送Ajax请求

            */

            function queryData(url) {

                return new Promise((resolve, reject) => {

                    var xhr = new XMLHttpRequest();

                    xhr.onreadystatechange = function() {

                        if (xhr.readyState != 4) return;

                        if (xhr.readyState == 4 && xhr.status == 200) {

                            // 处理正常情况

                            resolve(xhr.responseText);

                        } else {

                            // 处理异常情况

                            reject('接口请求失败');

                        }

                    };

                    xhr.responseType = 'text'; // 设置返回的数据类型

                    xhr.open('get', url);

                    xhr.send(null); // 请求接口

                });

            }

            // 发送多个ajax请求并且保证顺序

            queryData(' https://jsonplaceholder.typicode.com/posts ')

                .then(

                    data1 => {

                        console.log(JSON.stringify(data1));

                        return queryData(' https://jsonplaceholder.typicode.com/posts ');

                    },

                    error1 => {

                        console.log(error1);

                    }

                )

                .then(

                    data2 => {

                        console.log(JSON.stringify(data2));

                        // 返回普通值

                        return 'qianguyihao';

                    },

                    error2 => {

                        console.log(error2);

                    }

                )

                /*

                    既然上方返回的是 普通值,那么,这里的 then 是谁来调用呢?

                    答案是:这里会产生一个新的 默认的 promise实例,来调用这里的then,确保可以继续进行链式操作。

                */

                .then(data3 => {

                    // 这里的 data3 接收的是 普通值 'qianguyihao'

                    console.log(data3);

                });

        </script>

Promise常用的API:实例方法

1.Promise.prototype.then()

Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。

 

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

getJSON("/post/1.json").then(function(post) {

  return getJSON(post.commentURL);

}).then(function (comments) {

  console.log("resolved: ", comments);

}, function (err){

  console.log("rejected: ", err);

});

上面代码中,第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,就调用第一个回调函数,如果状态变为rejected,就调用第二个回调函数。

2. Promise.prototype.catch()

Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

getJSON('/posts.json').then(function(posts) {

  // ...

}).catch(function(error) {

  // 处理 getJSON 和 前一个回调函数运行时发生的错误

  console.log('发生错误!', error);

});

上面代码中,getJSON()方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then()方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch()方法指定的回调函数,处理这个错误。另外,then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

getJSON('/post/1.json').then(function(post) {

  return getJSON(post.commentURL);

}).then(function(comments) {

  // some code

}).catch(function(error) {

  // 处理前面三个Promise产生的错误

});

上面代码中,一共有三个 Promise 对象:一个由getJSON()产生,两个由then()产生。它们之中任何一个抛出的错误,都会被最后一个catch()捕获。

一般来说,不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。

不管是then()方法还是catch()方法,都返回了一个新的Promise对象

3. Promise.prototype.finally()

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。

不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。

finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

下面是一个例子,包含了三种实例方法

<script>

            function queryData() {

                return new Promise((resolve, reject) => {

                    setTimeout(function() {

                        var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的数据

                        if (data.retCode == 0) {

                            // 接口请求成功时调用

                            resolve(data);

                        } else {

                            // 接口请求失败时调用

                            reject({ retCode: -1, msg: 'network error' });

                        }

                    }, 100);

                });

            }

            queryData()

                .then(data => {

                    // 从 resolve 获取正常结果

                    console.log('接口请求成功时,走这里');

                    console.log(data);

                })

                .catch(data => {

                    // 从 reject 获取异常结果

                    console.log('接口请求失败时,走这里');

                    console.log(data);

                })

                .finally(() => {

                    console.log('无论接口请求成功与否,都会走这里');

                });

        </script>

Promise 的常用API:对象方法

1.Promise.all():并发处理多个异步任务,所有任务都执行成功,才能得到结果。

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

p的状态由p1、p2、p3决定,分成两种情况。

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

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

2.Promise.race(iterable): 并发处理多个异步任务,只要有一个任务执行成功,就能得到结果。

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

只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

3. Promise.allSettled()

用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做”Settled“,包含了”fulfilled“和”rejected“两种情况。

Promise.allSettled()方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更。

4. Promise.any()

只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

Promise.any()跟Promise.race()方法很像,只有一点不同,就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。

 async/await的作用与用法

什么是async、await?

async基本用法

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

下面是一个例子。

async function getStockPriceByName(name) {

  var symbol = await getStockSymbol(name);

  var stockPrice = await getStockPrice(symbol);

  return stockPrice;

}

getStockPriceByName('goog').then(function (result) {

  console.log(result);

});

上面代码是一个获取股票报价的函数,函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。

async 函数有多种使用形式。

// 函数声明

async function foo() {}

// 函数表达式

const foo = async function () {};

// 对象的方法

let obj = { async foo() {} };

obj.foo().then(...)

// Class 的方法

class Storage {

  constructor() {

    this.cachePromise = caches.open('avatars');

  }

  async getAvatar(name) {

    const cache = await this.cachePromise;

    return cache.match(`/avatars/${name}.jpg`);

  }

}

const storage = new Storage();

storage.getAvatar('jake').then(…);

// 箭头函数

const foo = async () => {};

async函数返回一个 Promise 对象。

async函数内部return语句返回的值,会成为then方法回调函数的参数。

async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。

Promise 对象的状态变化

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

 await

正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。

await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。

只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。

有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。

另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。

错误处理

如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject。

防止出错的方法,也是将其放在try...catch代码块之中

如果有多个await命令,可以统一放在try...catch结构中。

使用注意点

第一点,  前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。

第二点,  多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

第三点,  await命令只能用在async函数之中,如果用在普通函数,就会报错。

async 函数的实现原理

async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

与其他异步处理方法的比较

我们通过一个例子,来看 async 函数与 Promise的比较。

假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。

首先是 Promise 的写法。

function chainAnimationsPromise(elem, animations) {

  // 变量ret用来保存上一个动画的返回值

  var ret = null;

  // 新建一个空的Promise

  var p = Promise.resolve();

 

  // 使用then方法,添加所有动画

  for(var anim of animations) {

    p = p.then(function(val) {

      ret = val;

      return anim(elem);

    });

  }

 

  // 返回一个部署了错误捕捉机制的Promise

  return p.catch(function(e) {

    /* 忽略错误,继续执行 */

  }).then(function() {

    return ret;

  });

}

虽然 Promise 的写法比回调函数的写法大大改进,但是一眼看上去,代码完全都是 Promise 的 API(then、catch等等),操作本身的语义反而不容易看出来。

async 函数的写法。

async function chainAnimationsAsync(elem, animations) {

  var ret = null;

  try {

    for(var anim of animations) {

      ret = await anim(elem);

    }

  } catch(e) {

    /* 忽略错误,继续执行 */

  }

  return ret;

}

可以看到Async函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。

 

参考:

https://www.cnblogs.com/sybboy/p/6420812.html

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/await

https://blog.csdn.net/weixin_44801790/article/details/126345725

https://www.cnblogs.com/qianguyihao/p/12660393.html