乐趣区

关于javascript:JavaScript学习笔记一-promise和asyncwait

前言

最近我在学习前端相干的常识,没有意识到的一个问题是我用的 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…
退出移动版