前言

最近我在学习前端相干的常识,没有意识到的一个问题是我用的Ajax是异步的,如上面代码所示:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body><div id = "app">    <h2>{{ product }} X are in stock .</h2>    <ul v-for = "product in products" >        <li>            <input type="number" v-model.number="product.quantity">                      {{product.name}}             <span v-if = "product.quantity === 0">                曾经售空            </span>            <button @click = "product.quantity += 1">                 增加            </button>        </li>     </ul>    <h2> 总库存 {{ totalProducts}}</h2></div><div>    app.products.pop();</div><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>    const app = new Vue({        el:'#app',        data:{            product:'Boots',            products:[                'Boots',                'Jacket'            ]        },        computed:{            totalProducts(){                return this.products.reduce((sum,product) => {                    return sum + product.quantity;                },0);            }        },        created(){            fetch('http://localhost:8080/hello').then(response=> response.json()).then(json => {                console.log("0");                this.products = json.products;            })              fetch('http://localhost:8080/hello').then(response=> response.json()).then(json => {                console.log("1");            })          }    })</script></body></html>

依照我的了解是应该先输入0,后输入1。但事实上并非如此,因为fetch是异步的,这里的异步咱们能够简略的了解为执行fetch的执行者并不会马上执行,所以会呈现1先在控制台输入这样的状况:

但我的欲望是在第一个fetch工作执行之后再执行第二个fetch工作, 其实能够这么写:

fetch('http://localhost:8080/hello').then(response=> response.json()).then(json => {                console.log("0");                this.products = json.products;                fetch('http://localhost:8080/hello').then(response=> response.json()).then(json => {                console.log("1");      })  })  

这看起来像是一种解决方案,那如果说我有A、B、C、D四个工作都是异步的都想按A B C D这样的程序写,如果向下面的写法,嵌套调用,那看起来就相当的不体面。由此就引出了promise。

promise

根本应用

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    </body><script>      new Promise(function(resolve, reject) {              setTimeout(()=>{                    console.log(0);                    resolve("1");                   },3000)     }).then((result)=> console.log(result)); </script></html> 

如下面代码所示,咱们申明了一个Promise对象,并向其传递了一个函数,函数的行为是3秒后再控制台输入0,并调用resolve函数,resolve函数会触发then()。所以咱们下面的两个fetch用promise就能够这么写。

fetch('http://localhost:8080/hello').then(response=> response.json()).then(json => {   console.log("0");   this.products = json.products;               }).then(()=>{     fetch('http://localhost:8080/hello').then(response=> response.json()).then(json => {         console.log("1");     })  })  

fetch间接返回的就是Promise对象,then之后也还是一个Promise对象。Promise对象的构造函数语法如下:

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

对于一个工作来说,工作最终状态有两种: 一种是执行胜利、一种是执行失败。参数resolve和reject是由JavaScript本身提供的回调,是两个函数, 由 JavaScript 引擎事后定义,因而咱们只须要在工作的胜利的时候调用resolve,失败的时候reject即可。留神工作的最终状态只能是胜利或者失败,你不能先调用resolve后调用reject,这样工作就会被认定为失败状态。留神,如果工作在执行的过程中呈现了什么问题,那么应该调用reject,你能够传递给reject任意类型的参数,然而倡议应用Error对象或继承自Error的对象。

Promise的状态

一个Promise刚创立工作未执行结束,状态处于pending。在工作执行结束调用resolve时,状态转为fulfilled。工作执行出现异常调用error,状态转为reject。

then、catch、finally

then的中文意为而后,连贯了解起来也比拟天然,工作执行结束而后执行,语法如下:

promise.then(  function(result) { /* 解决工作失常执行的后果 */ },   function(error) { /* 解决工作异样执行的后果 */ });

第一个函数将在 promise resolved 且接管到后果后执行。第二个函数将在 promise rejected 且接管到error信息后执行。

上面是示例:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    </body><script>    let promiseSuccess = new Promise(function(resolve,reject){        setTimeout(() => resolve("工作正确执行结束"),1000);        })    promiseSuccess.then(result=>{        alert(result);    });    let promiseFailed = new Promise(function(resolve,reject){        setTimeout(() => reject("工作执行出现异常"),1000);        })    promiseFailed.then(result=>{        alert(result);    },error=>{        alert(error);    });</script></html>

如果你只想关注工作执行产生异样,能够将null作为then的第一个参数, 也能够应用catch:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    </body><script>    new Promise(function(resolve,reject){        reject("产生了谬误");    }).catch(error=>{        alert(error);    })    new Promise(function(resolve,reject){        reject("产生了谬误");    }).then(null,error=> alert(error));</script></html>

像try catch 中的finally子句一样,promise也有finally,不同于then,finally没有参数,咱们不晓得promise是否胜利,不过关系,finally的设计用意是执行“惯例”的实现程序。当promise中的工作完结,也就是调用resolve或reject,finally将会被执行:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    </body><script>    new Promise(function(resolve,rejetc){          resolve("hello");       }).then(result => alert(result),null).finally(()=>alert("执行清理工作中"));</script></html>

then catch finally 的执行程序为跟在Promise前面的程序。

Promise链与错误处理

通过下面的探讨,咱们曾经对Promise有一个根本的理解了,如果咱们有一系列异步工作,为了探讨不便让咱们把它称之为A、B、C、D,异步工作的特点是尽管他们在代码中的程序是A、B、C、D,但理论执行程序可能四个字母的排列组合,然而咱们心愿是A、B、C、D,那咱们用Promise能够这么写:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    </body><script>    new Promise(function(resolve,reject){        alert("我是A工作");        resolve("请B工作执行");    }).then(result=>{       alert(result+"收到申请,B工作开始执行");       return "B 工作执行结束,请C工作开始执行";    },null).then(result=>{        alert(result+"收到申请,C工作开始执行");        return "C 工作执行结束,请D工作开始执行";    },null).then(result =>{          alert(result+"D工作开始执行,按A、B、C、D序列执行结束");    },null)</script></html>

运行流程为: 初始化promise,而后开始执行alert后resolve,此时后果滑动到最近的处理程序then,then处理程序被调用之后又创立了一个新的Promise,并将值传给下一个处理程序,以此类推。随着result在处理程序链中传递,咱们能够看到一系列alert调用:

  • 我是A工作
  • 请B工作执行收到申请,B工作开始执行
  • B 工作执行结束,请C工作开始执行收到申请,C工作开始执行
  • C 工作执行结束,请D工作开始执行D工作开始执行,按A、B、C、D序列执行结束

这样做之所以是可行的,是因为每个对.then的调用都会返回了一个新的promise,因而咱们能够在其之上调用下一个.then。

当处理程序返回一个值时(then),它将成为该promise的result,所以将应用它调用下一个.then。

下面的是按A、B、C、D执行,咱们也会期待A工作执行之后,B、C、D都开始执行,这相似于跑步较量,A是开发令枪的人,听到信号,B、C、D开始跑向起点。上面是一个示例:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    </body><script>let promise  = new Promise(function(resolve,reject){    setTimeout(() => resolve(1),1000);})promise.then(function(result){    alert(result); // 1    return result * 2;})promise.then(function(result){    alert(result); // 1    return result * 2;})promise.then(function(result){    alert(result); // 1    return result * 2;})</script></html>

弹出后果将只会是1。

Promise API

Promise.all

假如咱们心愿并行执行多个工作,并且期待这些工作执行结束才进行下一步,Promise为咱们提供了Promise.all:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    </body><script>   var  proOne  = new Promise(resolve=> setTimeout(()=> resolve(1),3000);   var  proTwo  = new Promise(resolve=> setTimeout(()=> resolve(2),2000);   var proThree = new Promise(resolve=> setTimeout(()=> resolve(3),1000));   Promise.all([proOne,proTwo,proThree]).then(alert);</script></html>

即便proOne工作破费工夫最长,但它的后果仍让是数组中的第一个。如果人一个promise失败即调用了reject,那么Promise.all就被了解reject,齐全疏忽列表中其余的promise:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    </body><script>   var  proOne  = new Promise(resolve=> setTimeout(()=> resolve(1),1000));   var  proTwo  = new Promise((resolve,reject)=> setTimeout(()=> reject(2),1000));   var  proThree = new Promise(resolve=> setTimeout(()=> resolve(3),1000));   Promise.all([proOne,proTwo,proThree]).catch(alert);</script></html>

最初会弹出2。

Promise的语法如下:

// iterable 代表可迭代对象let promise = Promise.all(iterable);

iterable 能够呈现非Promise类型的值如下图所示:

Promise.all([  new Promise((resolve, reject) => {    setTimeout(() => resolve(1), 1000)  }),  2,  3]).then(alert); // 1, 2, 3

Promise.allSettled

Promise.all中任意一个promise的失败,会导致最终的工作失败,然而有的时候咱们心愿看到所有工作的执行后果,有三个工作A、B、C执行,即便A失败了,那么B和C的后果咱们也是关注的,由此就引出了Promise.allSettled:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    </body><script>// 这三个地址用于获取用户信息。// 即便一个失败了咱们依然对其余的感兴趣let urls = [  'https://api.github.com/users/iliakan',  'https://api.github.com/users/remy',  'https://no-such-url'];Promise.allSettled(urls.map(url => fetch(url)))  .then(results => { // (*)    results.forEach((result, num) => {      if (result.status == "fulfilled") {        alert(`${urls[num]}: ${result.value.status}`);      }      if (result.status == "rejected") {         alert(`${urls[num]}: ${result.reason}`);      }    });  });</script></html>

留神Promise.allSettled是新增加的个性,旧的浏览器可能不反对,那就须要咱们包装一下,更为业余的说法是polyfill:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    </body><script>    if(!Promise.allSettled){        const rejectHandler = reason => ({status:'rejected',reason});        const resolveHandler = value => ({status:'fulfilled',value});        Promise.allSettled = function(promises){            const convertedPromises = promises.map(p => Promise.resolve(p)).then(resolveHandler,rejectHandler);            Promise.all(convertedPromises);        }    }</script></html>

promises通过map中的Promise.resolve(p)将输出值转换为Promise,而后对每个Promise都增加.then处理程序。这样咱们就能够应用Promise.allSettled来获取所有给定的promise的后果,即便其中一些被reject。

Promise.race

有的时候咱们也只关注第一名,那该怎么做呢? 由此引出Promise.race:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    </body><script> Promise.race([    new Promise((resolve,reject) => setTimeout(()=> resolve(1),1000)),    new Promise((resolve,reject) => setTimeout(()=> resolve(2),2000)),       new Promise((resolve,reject) => setTimeout(()=> resolve(3),2000)) ]).then(alert);</script></html>

这里第一个Promise最快,所以alert(1)。

Promise.any

但最快有可能是犯规的对吗? 即这个工作reject,有的时候咱们只想关注没有违规的第一名,由此引出Promise.any:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    </body><script> Promise.race([    new Promise((resolve,reject) => setTimeout(()=> reject(1),1000)),    new Promise((resolve,reject) => setTimeout(()=> resolve(2),2000)),       new Promise((resolve,reject) => setTimeout(()=> resolve(3),2000)) ]).then(alert);</script></html>

尽管第一个promise最快,然而它犯规了,所以alert的是2。那要是全犯规了呢, 那就须要catch一下:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=S, initial-scale=1.0">    <title>Document</title></head><body>    </body><script>     Promise.any([    new Promise((resolve,reject) => setTimeout(()=> reject("犯规1"),1000)),    new Promise((resolve,reject) => setTimeout(()=> reject("犯规2"),2000)) ]).catch(error =>{    console.log(error.constructor.name); // AggregateError    console.log(error.errors[0]); // 控制台输入犯规1     console.log(error.errors[1]); // 控制台输入犯规2  } )</script></html>

async/await 简介

有的时候异步办法可能比拟宏大,间接用Promise写可能不大雅观,由此咱们就引出async/await, 咱们只用在须要用到Promise的异步函数上写上async这个关键字,这个函数的返回值将会主动的被包装进Promise:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    </body><script>async function f() {  return 1;}f().then(alert); // 1</script></html>

然而咱们经常会碰到这样一种状况, 咱们封装了一个函数,这个函数上有async,然而外部的实现有两个操作A、B,B操作须要等到A实现之后再执行,由此就引出了await:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=    , initial-scale=1.0">    <title>Document</title></head><body></body><script>async function demo(){     let responseOne = fetch('http://localhost:8080/hello');     console.log("1");     let json  = await responseOne.json; // 语句一     let response = fetch('http://localhost:8080/hello');     let jsonTwo  = await responseOne.json; // 语句二     console.log("2");}demo(); </script></html>

这里用async/wait改写了咱们结尾的例子,JavaScript引擎执行到语句一的时候会期待fetch操作实现,才执行await后的代码。这一看如同清新了许多。留神await不能在没有async的函数中执行。相比拟于Promise.then,它只是获取promise后果的一个更加优雅的写法,并且看起来更易于读写。那如果期待的promise产生了异样呢,该怎么进行错误处理呢?有两种抉择,一种是try catch,另一种是用异步函数返回的promise产生的catch来解决这个谬误:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=    , initial-scale=1.0">    <title>Document</title></head><body>    </body><script>async function demo01(){    try{         let response = await fetch('/no-user-here');         let user = await response.json();    }catch(error){        alert(error);    }}demo01(); async function demo02(){    let response = await fetch('/no-user-here');    let user = await response.json();    }demo02().catch(alert);</script></html>

总结一下

JavaScript也有异步工作,如果咱们想协调异步工作,就能够抉择应用Promise,以后的咱们只有一个异步工作,咱们心愿在异步工作回调,那么能够在用Promise.then,如果你想关注谬误后果,那么能够用catch,如果你想在工作实现之后做一些清理工作,那么能够用Promise的finally。当初咱们将异步工作的数目晋升,晋升到三个,如果咱们想再这三个工作实现之后触发一些操作,那么咱们能够用Promise.all,然而但Promise.all的缺点在于,一个工作失败之后,咱们看不到胜利工作的后果,如果工作胜利与失败的后果,那么就能够用Promise.allSettled。但有的时候咱们也指向关注“第一名”,那就用Promise.race,但有的时候咱们也只想要没犯规的第一名,这也就是Promise.any。有的时候咱们也不想用then回调的这种形式,这写起来可能有点烦,那就能够用async/await 。

参考资料

  • 《古代JavaScript教程》 https://zh.javascript.info/as...