共计 12813 个字符,预计需要花费 33 分钟才能阅读完成。
前言
最近我在学习前端相干的常识,没有意识到的一个问题是我用的 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…