1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > JS - 15 - 异步 Promise async await

JS - 15 - 异步 Promise async await

时间:2020-08-05 08:35:15

相关推荐

JS - 15 - 异步 Promise async await

Promise 类似 java 的 Callable

then 方法 类似 java 的 Future

下一篇: 《JS - 16 - 任务调度、宏任务、微任务、轮询》

PromiseA+规范(英文原文) - /

PromiseA+规范(中文) - /p/e0f91e03d6c1

视频:/video/av82587599?p=22

文章目录

# “异步” 引入# 定时器轮询# 通过文件依赖,了解任务排列# 禁止套娃(嵌套)# Promise 微任务处理机制## 状态## then## 微任务、宏任务## 宏任务、微任务执行顺序 # 单一状态和状态中转# Promise.then 也是 promise# return {then(resolve, reject):{...}}# 使用 Promise 封装 ajax 异步请求# Promise 错误检查 与 catch# 自定义 错误# finally# 例子:异步加载图片# 封装 setTimeOut# 扁平化的 setInterval# (优化)定时器轮询 - 解决套娃# 加载文件 - (Promise 形式)# Promise.resolve 缓存后台数据# Promise.all 批量获取数据## 批量 ajax 接收 (加缓存) # Promise.allSettled 状态收集## 过滤ajax请求失败数据 # Promise.race 只接收最快完成的一个# Promise 队列原理## 1## 2 优化:抽离## 优化 reduce # 案例:队列 ajax 、渲染# 语法糖 : async 、 await## async## await # 语法糖简化 ajax 请求# 案例:进度条# class + async 、 await## 初尝禁果## 异步封装在类内部 # await 并行执行技巧## 方法一

# “异步” 引入

js 会把 主线程上某些任务放入任务队列

当主线程完成后,不断轮询任务队列

function loadImag(src, resolve, reject) {let image = new Image() ;image.src = src ; image.onload = resolve ;image.onerror = reject ;}loadImag('./images/0fcee7d8fb636a7e86ee034ac15cc46130d404c7.jpg',() => {console.log('图片加载完成')}, () => {console.log('加载失败')}) ;console.log('后盾热') ;

# 定时器轮询

div 向右移动,缩小

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>div{background-color: aqua;width: 300px;height: 300px;position: absolute;}</style></head><body><div></div><script>function interval(callback,delay=100) {let id = setInterval(() => callback(id), delay) ;}interval((id) => {const div = document.querySelector('div') ;let left = parseInt(window.getComputedStyle(div).left)div.style.left = left + 10 + 'px' ;if(left>=200) {clearInterval(id)interval((id) => {let width = parseInt(window.getComputedStyle(div).width) ;div.style.width = width - 10 + "px" ;if(width<=20) {clearInterval(id)}}) }})</script></body></html>

# 通过文件依赖,了解任务排列

加载两个文件,hd.js 和 hd2.js

其中

hd2.js 对 hs.js 有依赖关系

但由于 加载和 加载完 两个事件是没有任务队列的。

因此会有概率出现,hd.js 加载完之前 加载了 hd2.js 。

而 hd2.js 缺少 hd.js 的依赖,从而hd2.js 报错

(如下)

function load(src,resolve) {let script = document.createElement('script') ;script.src = src ; script.onload = resolve ; document.body.append(script) ;}load('./dist/hd.js', () => {show();}) ;load('./dist/hd2.js', () => {console.log('hd2')})console.log('主进程')

hd.js

function show() {console.log('hd show')}

hd2.js

show() ;

所以,要让 加载和加载完 形成任务队列

需要把代码改一改

function load(src,resolve) {let script = document.createElement('script') ;script.src = src ; script.onload = resolve ; document.body.append(script) ;}load('./dist/hd.js', () => {// hd.js 加载完show();load('./dist/hd2.js', () => {// hd2.js 开始加载console.log('hd2')})}) ;console.log('主进程')

# 禁止套娃(嵌套)

下面 先开启后台服务 (我这里用 mock 工具 jsonserver - 光速使用)

开启服务:

json-server --watch db.json

db.json 数据:

{"users": [{"id":1, "name":"御坂美琴", "email":"jdbc@"},{"id":2, "name":"茵蒂克丝", "email":"c3p0@"}],"lessons": [{"id":1 ,"js":60, "ts":89},{"id":2 ,"js":33, "ts":10}]}

ajax.js :

function ajax(url, callback) {let xhr = new XMLHttpRequest() ;xhr.open('GET', url) ;// xhr.readyState==4 ==> xhr.onloadxhr.onload = function() {if(this.status == 200) {callback(JSON.parse(this.response)) ;}else {throw new Error('加载失败')}}xhr.send() ;}

index.html:

<script src="./dist/ajax.js"></script><script>ajax(`http://localhost:3000/users?name=御坂美琴`,(result) => {let user = result[0] ;ajax(`http://localhost:3000/lessons?id=${user.id}`, (lessons) => {console.log(lessons)});} );console.log('main 线程')</script>

业务完成了,但是代码一层套一层,不美观

这种套娃也有中二叫法:回调地狱

怎么避免?

# Promise 微任务处理机制

Promise 许诺

白话:等通知模式

## 状态

panding 待定的(准备状态)

let p = new Promise((resolve, reject)=>{})console.log(p) ;

resolved 成功状态

let p = new Promise((resolve, reject)=>{resolve('成功状态');})console.log(p) ;

reject 拒绝状态

let p = new Promise((resolve, reject)=>{reject('拒绝状态');})console.log(p) ;

## then

then 里面做处理

value 成功参数reason 失败参数

let p = new Promise((resolve, reject)=>{reject('拒绝状态');}).then(value => {console.log('成功的处理')}, (reason)=> {console.log('拒绝的处理')})console.log(p) ;

let p = new Promise((resolve, reject)=>{resolve('成功状态');//reject('拒绝状态');}).then(value => {console.log('成功的处理')}, (reason)=> {console.log('拒绝的处理')})console.log(p) ;

## 微任务、宏任务

同级情况下,微任务为主(优先级较高)

<script src="./dist/ajax.js"></script><script>let p = new Promise((resolve, reject)=>{resolve('成功状态');//reject('拒绝状态');}).then(value => {console.log('微任务1:成功的处理--1')}, (reason)=> {console.log('微任务1:拒绝的处理--1')}).then(value => {console.log('微任务1:成功的处理--2')}, (reason)=> {console.log('微任务1:拒绝的处理--2')})console.log(p) ;ajax(`http://localhost:3000/users`, (result) => {console.log('宏任务',result)})</script>

## 宏任务、微任务执行顺序

同级下,微任务优先级高

setTimeout(()=> {console.log('宏任务') // 宏任务等待微任务完成后执行}, 0) ;new Promise(resolve => {resolve();// 把then中成功的代码放入微任务区console.log('main promise') ;}).then(value => {console.log('微任务 成功') // 微任务等在同步线程完成后执行});console.log('main 线程完')

不同级下,顺序不变

let promise = new Promise(resolve => {console.log("setTimeout")resolve();console.log('promise')}).then(value => {console.log('成功');});console.log('houdunren.,com')

# 单一状态和状态中转

如果 上级 reject ,即便下级resolve也会去到 error 回调

let p1 = new Promise((resolve, reject) => {reject('p1 失败')}) ;let p2 = new Promise((resolve, reject) => {resolve(p1) ;}).then((value)=> {console.log("p2 success ",value)}, (error) => {console.log("p2 error " , error)})console.log('main finish')

并且,状态确定之后,是不变的

let p2 = new Promise((resolve, reject) => {reject("失败") ; // 确定了失败resolve("成功") ; // 后面的这段代码想改变状态,是无法做到的}).then((value)=> {console.log("p2 success ",value)}, (error) => {console.log("p2 error " , error)})console.log('main finish')

# Promise.then 也是 promise

且 失败 被成功处理了, 返回的也是成功

# return {then(resolve, reject):{…}}

then 中返回 reject 结果 可以这样写(下图)

new Promise((resolve, reject)=> {resolve("1 ok") ;}).then(value => {console.log(value) // 1 okreturn {then(resolve, reject) {reject('2 reject')}}}).then(null, reason => {console.log(reason) // 2 reject ;})

或者

new Promise((resolve, reject)=> {resolve("1 ok") ;}).then(value => {console.log(value) // 1 okreturn class {static then(resolve, reject) {reject('2 reject')}}}).then(null, reason => {console.log(reason) // 2 reject ;})

原理后面说

当然,也能 写成

....then( value => {return new Promise((resolve, reject) => reject())})...

但是就很蠢

# 使用 Promise 封装 ajax 异步请求

<script src="./dist/ajax.js"></script><script>ajax(`http://localhost:3000/users?name=茵蒂克丝`).then(value => {let user = value[0] ;return ajax(`http://localhost:3000/lessons?id=${user.id}`) ;}).then(value => {console.log('ok',value);}, reason => {console.log('error', reason) ;})</script>

js 返回 Promise 对象

function ajax(url) {return new Promise((resolve, reject) => {let xhr = new XMLHttpRequest() ;xhr.open('GET', url) ;// xhr.readyState==4 ==> xhr.onloadxhr.onload = function(result) {if(this.status == 200) {resolve(JSON.parse(this.response) ) ;}else {reject('加载失败')}}xhr.onerror = function() {reject(this)}xhr.send() ;})}

# Promise 错误检查 与 catch

// throw new Error('promise fail')// hd + 1 ; resolve('rejected');}).then(value => new Promise((resolve, reject)=> {reject('失败') ;}) , // reason => {} ).then(value => {},//reason => {} ).catch((reason) => {// 语法上,也可以放中间, 但通常放最后console.log(reason)}) ;

# 自定义 错误

ajax.js

class ParamError extends Error{constructor(msg) {super(msg) ; this.name = 'ParamError' ;}}class HttpError extends Error {constructor(msg) {super(msg) ;this.name = 'HttpError' ;}}class WebAssert {static isURL(url) {if(!/^https?:\/\//i.test(url)) {throw new ParamError('请求地址格式错误')}}}function ajax(url) {return new Promise((resolve, reject) => {WebAssert.isURL(url) ;let xhr = new XMLHttpRequest() ;xhr.open('GET', url) ;// xhr.readyState==4 ==> xhr.onloadxhr.onload = function(result) {if(this.status == 200) {resolve(JSON.parse(this.response) ) ;}else if(this.status ==404) {// throw new HttpError('用户不存在'); 这样是不行的,因为在新线程里面reject(new HttpError('用户不存在'))}else{reject('加载失败')}}xhr.onerror = function() {reject(this)}xhr.send() ;})}

<script src="./dist/ajax.js"></script><script>ajax('http://localhost:3000/users1').then(value => {console.log(value) ;}).catch((err) => {if(err instanceof ParamError) {console.log(err.message) ;}else if(err instanceof HttpError) {alert(err.message)}else {console.log(err)}})</script>

这里故意不存在的 请求

# finally

最终执行

例子:资源加载后,隐藏LOADINGDOM

在·

<div id='loading'>loading...</div><script src="./dist/ajax.js"></script><script>ajax(`http://localhost:3000/users?name='茵蒂克丝'`).then(value=> {console.log(value);}).finally(()=> {loading.style.display='none';})</script>

class ParamError extends Error{constructor(msg) {super(msg) ; this.name = 'ParamError' ;}}class HttpError extends Error {constructor(msg) {super(msg) ;this.name = 'HttpError' ;}}class WebAssert {static isURL(url) {if(!/^https?:\/\//i.test(url)) {throw new ParamError('请求地址格式错误')}}}function ajax(url) {return new Promise((resolve, reject) => {loading.style.display='block'WebAssert.isURL(url) ;let xhr = new XMLHttpRequest() ;xhr.open('GET', url) ;// xhr.readyState==4 ==> xhr.onloadxhr.onload = function(result) {if(this.status == 200) {resolve(JSON.parse(this.response) ) ;}else if(this.status ==404) {// throw new HttpError('用户不存在'); 这样是不行的,因为在新线程里面reject(new HttpError('用户不存在'))}else{reject('加载失败')}}xhr.onerror = function() {reject(this)}xhr.send() ;})}

# 例子:异步加载图片

function loadImage(src) {return new Promise((resolve, reject)=> {const image = new Image() ;image.src = src ;image.onload = () => {resolve(image)} ;image.onerror = reject;document.body.appendChild(image)})}loadImage('./images/0fcee7d8fb636a7e86ee034ac15cc46130d404c7.jpg').then(image=> {image.style.border = 'solid 6px red'})

# 封装 setTimeOut

function timeout(delay = 1000) {return new Promise(resolve => {setTimeout(resolve, delay);})}timeout(2000).then((value) => {console.log('定时器 - 1',value)return timeout(2000) ;}).then((value) => {console.log('定时器 - 2', value)})

# 扁平化的 setInterval

function interval(delay=1000, callback) {return new Promise((resolve, reject) => {let id = setInterval(() => {callback(id, resolve);}, delay);})}interval(100, (id, resolve) =>{console.log(12) ;clearInterval(id) ;resolve('定时器关闭')}).then((value)=> {console.log(value)}) ;

# (优化)定时器轮询 - 解决套娃

好,现在可以处理前面 “回调地狱” 的问题了

同样的例子

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>div{background-color: aqua;width: 300px;height: 300px;position: absolute;}</style></head><body><div></div><script>function interval(callback,delay=100) {return new Promise((resolve, reject) => {let id = setInterval(() => callback(id, resolve, reject), delay) ;}) ;}const div = document.querySelector('div') ;interval((id, resolve) => {let left = parseInt(window.getComputedStyle(div).left)div.style.left = left + 10 + 'px' ;if(left>=200) {clearInterval(id)resolve('右移完毕!')}}, 100).then(value=> {interval((id, resolve) => {let width = parseInt(window.getComputedStyle(div).width) ;div.style.width = width - 10 + "px" ;if(width<=20) {clearInterval(id)resolve('缩小完毕!')}}) }, 100)</script></body></html>

# 加载文件 - (Promise 形式)

function loadScript(url) {return new Promise((resolve, reject) => {let script = document.createElement('script');script.src = url;script.onload = resolve(script);script.reject = reject;document.body.appendChild(script)})}// hd.js// -----------------// function show() {// console.log('hd show')// }//// hd2.js// -----------------// show() ;loadScript('./dist/hd.js').then(script => {console.log(script);return loadScript('./dist/hd2.js')}).then(script => {console.log(script);});

# Promise.resolve 缓存后台数据

<div id="loading">loading</div><script src="./dist/ajax.js"></script><script>function query(name) {const cache = query.cache || (query.cache = new Map())let temp = cache.get(name) ; if(temp) return Promise.resolve(temp) ;let url = `http://localhost:3000/users?name=${name}` ;return ajax(url).then(user => {cache.set(name, user) ;return user ; })}query('茵蒂克丝').then(user => {console.log(user)})setTimeout(() => {query('茵蒂克丝').then(user => {console.log(user)})}, 1000);</script>

# Promise.all 批量获取数据

全部成功,就成功 (下图)

function timeout(msg,delay=1000) {return new Promise((resolve) => {setTimeout(() => {resolve(msg)}, delay);})}const t1 = timeout('第一个异步') ;const t2 = timeout('第二个异步') ;Promise.all([t1, t2]).then(result => {console.log(result)})

一个失败就失败 (下图)

const t1 = new Promise((resolve, reject) => {setTimeout(() => {reject('第一个异步')}, 1000);})const t2 = new Promise((resolve, reject) => {setTimeout(() => {resolve('第二个异步')}, 1000);})Promise.all([t1, t2]).then(result => {console.log('ok',result)}).catch(error => {console.log('error',error)})

失败处理了,也是成功 (下图)

const t1 = new Promise((resolve, reject) => {setTimeout(() => {reject('第一个异步')}, 1000);}).catch(error => {console.log('第一个异步 失败')return '1 error'}).finally(value => {console.log('finally')})const t2 = new Promise((resolve, reject) => {setTimeout(() => {resolve('第二个异步')}, 1000);})Promise.all([t1, t2]).then(result => {console.log('ok',result)}).catch(error => {console.log('error',error)})

## 批量 ajax 接收 (加缓存)

<script src="./dist/ajax.js"></script><script>function getUsers(names) {const cache = getUsers.cache ||( getUsers.cache = new Map() ) ;let promises = names.map(name => {let user = cache.get(name) ;if(user) return Promise.resolve(user) ;return ajax(`http://localhost:3000/users?name=${name}`).then(user => {cache.set(name, user) ;return Promise.resolve(user) ;})})return Promise.all(promises);}getUsers(['御坂美琴', '茵蒂克丝']).then(value => {console.log(value) ;}).catch(reason => {console.log("error", reason)})setTimeout(() => {getUsers(['御坂美琴', '茵蒂克丝']).then(value => {console.log(value) ;}).catch(reason => {console.log("error", reason)})}, 1000);</script>

# Promise.allSettled 状态收集

const p1 = new Promise((resolve, reject) => {reject('p1 reject') ;})const p2 = new Promise((resolve, reject) => {resolve('p2 resolve') ;})Promise.allSettled([p1, p2]).then(result => {console.log(result)})

## 过滤ajax请求失败数据

<script src="./dist/ajax.js"></script><script>function getUsers(names) {return names.map(name => {return ajax(`http://localhost:3000/users?name=${name}`).then(user => {return user.length>0?Promise.resolve(user) : Promise.reject('炮姐找不到,只有御坂美琴') ;})})}Promise.allSettled(getUsers(['茵蒂克丝', '炮姐'])).then(result => {console.log(result);})</script>

# Promise.race 只接收最快完成的一个

看下面一个 请求超时的例子

function query(url, delay=1000) {let promises = [new Promise((resolve, reject)=> {setTimeout(() => {reject('请求超时')}, delay);}),ajax(url)]return Promise.race(promises) ;}query(`http://localhost:3000/users`).then(result => {console.log(result)})

超时(下图)

# Promise 队列原理

## 1

function queue(num) {let promise = Promise.resolve() ;num.forEach(v=>{promise=promise.then(_=>{return new Promise(resolve => {setTimeout(() => {console.log(v)resolve() ;}, 1000);})})})}queue([1,2,3,4,5])

## 2 优化:抽离

把 Promise 抽出来

function queue(num) {let promise = Promise.resolve() ;num.forEach(v=>{promise=promise.then((value)=>{return v(value) ;})})}function p1() {return new Promise(resolve => {setTimeout(() => {console.log('p1')resolve();}, 1000);})}function p2() {return new Promise(resolve => {setTimeout(() => {console.log('p2')resolve();}, 1000);})}queue([p1, p2])

## 优化 reduce

function queue(num) {return num.reduce((promise, current) => {return promise.then(current)}, Promise.resolve());}function p1() {return new Promise(resolve => {setTimeout(() => {console.log('ok p1')resolve() }, 1000);})}function p2() {return new Promise(resolve => {setTimeout(() => {console.log('ok p2')resolve() }, 1000);})}queue([p1,p2])

# 案例:队列 ajax 、渲染

引入之前写好的 ajax.js

class User{ajax(user) {let url = `http://localhost:3000/users?name=${user}` ;return ajax(url);}render(users) {users.reduce((promise, user) => {return promise.then(()=> {return this.ajax(user) ;}).then(user => {return this.view(user);})}, Promise.resolve())}view(user) {return new Promise(resolve => {resolve();let h2 = document.createElement('h2') ;h2.innerHTML = JSON.stringify(user, null, 2)document.body.appendChild(h2) ;})}}new User().render(['茵蒂克丝','御坂美琴']);

# 语法糖 : async 、 await

## async

在方法前 加 async

方法的返回值就 变为了 Promise

async function hd() {return "" ;}let h = hd();console.log(h) ;// 是一个 Promise 状态 resolveh.then(v => {console.log(v)})

等于下面写法

function hd() {return new Promise(resolve => {resolve('')})}let h = hd();console.log(h) ;// 是一个 Promise 状态 resolveh.then(v => {console.log(v)})

## await

async function hd() {let name = await new Promise(resolve => {//resolve('await') // 不放行,后面不执行});console.log(name);}hd();

其实就是 then 的简写

function hd() {return new Promise((resolve) => {//resolve('await'); // 不放行,后面不执行}).then(name => {console.log(name)})}hd();

所以 可以这样写

async function hd() {const value = 'value 0'let value1 = await new Promise(resolve=> {setTimeout(() => {console.log(value)resolve('value 1 ');}, 1000)}) let value2 = await new Promise((resolve)=> {setTimeout(() => {console.log(value1)resolve('value 2');}, 1000);})return value2;}hd().then(value => {return new Promise(resolve => {setTimeout(() => {resolve();console.log(value)}, 1000);})})

# 语法糖简化 ajax 请求

<script src="./dist/ajax.js"></script><script>async function get(name) {let url = 'http://localhost:3000'let user = await ajax(`${url}/users?name=${name}`)return await ajax(`${url}/lessons?id=${user[0].id}`)}get('茵蒂克丝').then(value => {console.log(value)})

# 案例:进度条

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>div {height: 30px;background: #8e44ad;width:0px ;display: flex;justify-content: center;align-items: center;font-size: 30px;color: #fff;}</style></head><body><div id="loading">0%</div></body><script src="./dist/ajax.js"></script><script>function query(name) {let url = `http://localhost:3000` ;return ajax(`${url}/users?name=${name}`) ;}(async ()=>{let users = ['茵蒂克丝','御坂美琴','白井黑子','佐天泪子','初春饰利'];let len = users.length;for(let i=0;i<len;i++) {let user = await query(users[i]) ;let progress = ((i+1)/len)*100;loading.style.width = progress+'%';loading.innerHTML = Math.round(progress)+'%' ;console.log(user.length>0?user : '没有数据')}})()</script></html>

ajax.js

这里加了延时哦,为了看出效果

class ParamError extends Error{constructor(msg) {super(msg) ; this.name = 'ParamError' ;}}class HttpError extends Error {constructor(msg) {super(msg) ;this.name = 'HttpError' ;}}class WebAssert {static isURL(url) {if(!/^https?:\/\//i.test(url)) {throw new ParamError('请求地址格式错误')}}}function ajax(url) {return new Promise((resolve, reject) => {WebAssert.isURL(url) ;let xhr = new XMLHttpRequest() ;xhr.open('GET', url) ;// xhr.readyState==4 ==> xhr.onloadxhr.onload = function(result) {if(this.status == 200) {resolve(JSON.parse(this.response) ) ;}else if(this.status ==404) {// throw new HttpError('用户不存在'); 这样是不行的,因为在新线程里面reject(new HttpError('用户不存在'))}else{reject('加载失败')}}xhr.onerror = function() {reject(this)}setTimeout(() => {xhr.send() ;}, 1000);})}

db.json

{"users": [{"id":1, "name":"御坂美琴", "email":"jdbc@"},{"id":2, "name":"茵蒂克丝", "email":"c3p0@"}],"lessons": [{"id":1 ,"js":60, "ts":89},{"id":2 ,"js":33, "ts":10}]}

# class + async 、 await

一个类,内部如果有 then 方法,那么,他会被包装成 Promise

不会是个坑,会了真香

例如下面(简洁啊)

## 初尝禁果

<script src="./dist/ajax.js"></script><script>class User {constructor(name) {this.name = name;}then(resolve, reject) {const url = `http://localhost:3000`;let user = ajax(`${url}/users?name=${this.name}`);resolve(user);}}async function get() {let user = await new User('茵蒂克丝');console.log(user);}get();</script>

## 异步封装在类内部

class User {async get(name) {const url = `http://localhost:3000`;let list = await ajax(`${url}/users?name=${name}`);let user = list[0];user.name += '真平'return user;}}new User().get('茵蒂克丝').then(user => {console.log(user)})

# await 并行执行技巧

怎么让 await 并行?

## 方法一

await 在 接收参数时候才加上

function p1() {return new Promise(resolve => {setTimeout(() => {resolve('houdunren');}, 2000);})}function p2() {return new Promise(resolve => {setTimeout(() => {resolve('hdcms')}, 2000)})}async function hd() {let h1 = p1();let h2 = p2();let h1value = await h1;let h2value = await h2;console.log(h1value, h2value)}hd();

async function hd() {let res = await Promise.all([p1(), p2()]);console.log(res)}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。