异步与promise
异步编程
多线程:多个任务同时执行
单线程:网络请求,任务队列
主线程对任务队列不断地轮询,有任务进来了就执行;就不会出现阻塞的问题
异步加载图片
图片加载是比较慢的,这个任务是耗时比较长的;主线程通过事件循环不断地轮询;
这段代码的逻辑:
代码执行到loadImg这个函数,我们交给一个文件处理模块,处理完之后,把任务放到任务队列里面;
轮询这个任务队列,如果图片加载任务执行完毕,那么就会执行回调;
把主线程全部完成之后,轮询遍历任务队列,把里面所有任务执行完;处理任务通过回调
function loadImg(src,resolve,reject){
const img=new Image();
img.src=src;
img.onload=resolve;
img.onerror=reject;
}
loadImg('xxx',()=>{
console.log('图片加载完成');
},()=>{
console.log('图片加载失败');
})
JS异步编程,回调函数与promise - susana123 - 博客园
定时器例子
任务交给定时器处理的模块,任务抛到任务队列里面,不断轮询任务队列读取任务开始执行;
主线程执行完就去任务队列找有没有新的任务,回调就会被执行;
有专门的的timer模块管理定时器,到时间时会将回调放到任务队列中,主线程执行完会去任务队列中执行任务;
function interval(callback,delay){
let timerId=window.setInterval(()=>{
callback(timerId);
},delay)
}
interval((timerId)=>{
const div=document.querySelector('div');
const left=parseInt(window.getComputedStyle(div).left);
div.style.left=left+10+'px';
if(left>=200){
window.clearInterval(timerId);
interval((timerId)=>{
const width=parseInt(window.getComputedStyle(div).width);
div.style.width=width-10+'px';
if(width<=30){
clearInterval(timerId);
}
},50)
}
},50)
// 如果主线程执行很长时间,还是会卡死
// 注意:还是等主线程执行完,才会轮询看这个任务队列
for(let i=0;i<20000;i++){
console.log(i);
}
<body>
<div style="position:absolute;width:100px;height:100px;left:5px;background:green;"></div>
</body>
文件依赖加载
任务队列是谁先放进去的先干谁的活
function load(src,resolve){
const script=document.createElement('script');
script.src=src;
script.onload=resolve;
document.body.appendChild(script);
}
// 要注意:这2个是没有队列顺序的。 如果加载完了就抛任务,那就看这2个谁快了
// ps.如果这2个文件在不同的服务器上暴露的频率会更高一点
load('js/hd.js',()=>{
hd();
// 所以需要这样
load('js/houdunren.js',()=>{
houdunren();
})
})
load('js/houdunren.js',()=>{
houdunren();
})
ajax异步请求
通过http模块请求后台数据,之后往任务队列里面抛一个任务,主线程执行完后轮询任务集合,轮询之后就执行到相应回调。
这里的业务场景:如果请求之间存在依赖,必须要等待上一个请求结束之后才开始第二个请求
问题:回调会产生嵌套,即回调地狱
promise实例相关
pending状态 -》resolve/reject状态
.then是对状态的处理
微任务和宏任务的执行顺序
同步立刻执行
setTimeout放到任务队列里面准备执行
setTimeout(()=>{
console.log('setTimout');
},0)
new Promise(resolve=>{
resolve()
console.log('promise')
}).then(()=> console.log('then 1'))
console.log('houdunren');
Promise构造函数代码也是同步的
then/catch是属于异步的,微任务队列
优先级:
同步>微任务>宏任务 eventloop
有个有点难度的例子:
这里宏任务执行了才有微任务,没执行前微任务没有创建,宏任务执行之后再创建的微任务
宏任务已经拿到主线程里面执行了之后再创建的微任务,需要再下一次轮询时再执行这个微任务
new Promise(resolve=>{
setTimeout(()=>{
resolve()
console.log('setTimout');
},0)
console.log('promise')
}).then(()=> console.log('then 1'))
console.log('houdunren');
单一状态与状态中转
let p1=new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('拒绝');
// 上面已经设置了状态这一行是没有效果的
// 状态是单向的不可逆的
resolve('成功')
},5000)
})
new Promise((resolve,reject)=>{
// 这一行会产生微任务,下一次轮询时才会执行
// resolve('fullfield');
// 一定是要等待p1非pending状态才会中转
resolve(p1);
}).then((msg)=>{
console.log(msg)
},(error)=>{
console.log(error);
})
ES6---Promise 3: Promise状态和状态中转 - jane_panyiyun - 博客园
异步编程——回调函数/Promise/Async/Await_顾承要加油啊的博客-CSDN博客
https://segmentfault.com/a/1190000023903564
then链
then是对上一个promise状态的处理,then返回的也是一个promise
let p1=new Promise((resolve,reject)=>{
// resolve('fullfield')
// 这里reject then后面也是成功
reject('reject');
})
let p2=p1.then((msg)=>console.log(msg),(error)=>console.log(error))
.then(()=>console.log('成功'),()=>console.log('失败'))
console.log(p1);
console.log(p2);
setTimeout(()=>{
console.log('---setTimeout');
console.log(p1);
console.log(p2);
})
then的返回值
后面then是对前面(上一个)返回的promise的处理
-
then中return的值(普通值)能被下一个then接收到
-
当return一个promise时,对返回promise的处理
-pending状态会持续在等待它完成
new Promise((resolve,reject)=>{
resolve('fullfield')
}).then((msg)=>{
console.log(msg)
// return 'then 1 success'
return new Promise((resolve,reject)=>{
resolve('then 1 promise success')
})
},error=>console.log(error))
.then((msg)=>{
console.log(msg)
},error=>console.log(error))
-
只要返回的对象有then方法,同返回promise效果一样
看原型中有没有then方法
new Promise((resolve,reject)=>{
resolve('fullfield')
}).then((msg)=>{
console.log(msg)
return new Promise((resolve,reject)=>{
resolve('then 1 promise success')
})
// 1.返回一个有then方法的对象
return {
then(resolve,reject){
resolve('abc')
}
}
// 2.返回一个new MyPromise()实例
class MyPromise{
then(resolve,reject){
resolve('abc')
}
}
return new MyPromise();
// 3.返回一个有static方法的类
class MyPromise1{
static then(resolve,reject){
resolve('abc')
}
}
return MyPromise1;
},error=>console.log(error))
.then((msg)=>{
console.log(msg)
},error=>console.log(error))
封装ajax
function ajax(url){
return new Promise((resolve,reject)=>{
const xhr=new XMLHttpRequest();
xhr.open('GET',url);
xhr.send();
xhr.onload=function(){
if(this.status===200){
resolve(JSON.parse(this.response));
}else{
reject('加载失败')
}
}
xhr.onerror=function(){
reject(this);
}
})
}
ajax('xxx')
.then(user=> ajax(`xxx${user.id}`))
// 第2个处理错误的情况
// 把错误处理放在最后一个then,让它沿着then链一直找到顶级
.then((lessons)=> console.log(lessons),error=>console.log(error))
JavaScript中的promise的then()方法以及链式调用_Fighting社火底子-CSDN博客_promise.then链式调用
ES6 Promise对象then方法链式调用 - 后除 - 博客园
错误处理
promise对错误还是挺完善的:
不管是抛出错误还是系统级的语法错误,它也都能捕获到
使用catch统一对错误进行处理,并且是把它放到最后
如果之前你已经捕获特殊处理了,那就走你的错误回调
new Promise((resolve,reject)=>{
reject(new Error('error'))
// 抛出的错误还是系统判定错误
// throw new Error('throw error')
// hd + 1;
})
.then(value => console.log(value))
.then(value => console.log(value))
// 把catch放在最后
.catch(error => console.log(error))
自定义错误
class ParamError extends Error{
constructor(message){
super(message)
this.name='ParamError';
}
}
class HttpError extends Error{
constructor(message){
super(message)
this.name='HttpError';
}
}
function ajax(url){
return new Promise((resolve,reject)=>{
if(!/request$/.test(url)){
throw new ParamError('请求url错误')
}
const xhr=new XMLHttpRequest();
xhr.open('GET',url);
xhr.send();
xhr.onload=function(){
if(this.status===200){
resolve(JSON.parse(this.response));
}else if(this.status === 404){
reject(new HttpError('404未找到'))
}else{
reject('加载失败')
}
}
xhr.onerror=function(){
reject(this);
}
})
}
ajax('request')
.then(user=> ajax(`xxx${user.id}`))
// 第2个处理错误的情况
// 把错误处理放在最后一个then,让它沿着then链一直找到顶级
.then((lessons)=> console.log(lessons))
.catch(error=>{
if(error instanceof ParamError){
console.log(error.message);
}
if(error instanceof HttpError){
alert(error.message);
}
})
使用finally做加载效果
finally永远都会执行
ajax('xxx')
.then(user=> ajax(`xxx${user.id}`))
.finally(() => loading=false;)
封装异步加载图片
function loadImg(src){
return new Promise((resolve,reject)=>{
const img=new Image()
img.src=src;
img.onload=()=>{
resolve(img)
}
img.onerror=reject
})
}
loadImg('http://cdn.jsrun.top/css/img/ds_2.jpg').then((img)=>{
document.body.appendChild(img);
}).catch((error)=>console.log(error))
封装setTimout
function timeout(delay){
return new Promise(resolve=>{
setTimeout(()=>{
resolve();
},delay)
})
}
timeout(3000).then(()=>{
console.log('3ms is over');
return timeout(3000)
}).then(()=>{
console.log('3ms again');
})
封装setInterval
-
要终止,callback里要传递一个timerId
-
同时也需要改变promise状态,所以resolve也要暴露出去
-
resolve(div)
每一步是一个then方法,使用promise使得代码更加扁平化,避免了嵌套
function interval(callback,delay){
return new Promise(resolve=>{
let timerId=window.setInterval(()=>{
// 把resolve传递出去
callback(timerId,resolve);
},delay)
})
}
const div=document.querySelector('div');
interval((timerId,resolve)=>{
const left=parseInt(window.getComputedStyle(div).left);
div.style.left=left+10+'px';
if(left>=200){
window.clearInterval(timerId);
resolve()
}
},50).then(timerId=>{
return interval((timerId,resolve)=>{
const width=parseInt(window.getComputedStyle(div).width);
div.style.width=width-10+'px';
if(width<=30){
clearInterval(timerId);
resolve(div)
}
},50)
}).then(div=>{
div.style.background='red';
})
封装script文件加载
function load(src){
return new Promise((resolve,reject)=>{
const script=document.createElement('script');
script.src=src;
script.onload=()=>resolve(script);
script.onerror=reject;
document.body.appendChild(script);
})
}
load('js/hd.js')
.then(()=>{
hd();
return load('js/houdunren.js')
})
.then(()=> houdunren() )
.catch(()=>{})
Promise全局API
Promise.resolve
function ajax(url){
return new Promise((resolve,reject)=>{
const xhr=new XMLHttpRequest();
xhr.open('GET',url);
xhr.send();
xhr.onload=function(){
if(this.status===200){
resolve(JSON.parse(this.response));
}else{
// reject('加载失败')
resolve('abc');
}
}
xhr.onerror=function(){
reject(this);
}
})
}
function query(){
const cache=query.cache || (query.cache=new Map());
if(cache.has('user')){
console.log('走cache')
return Promise.resolve(cache.get('user'))
}
return ajax('xxx').then(value=>{
console.log('调接口')
cache.set('user',value);
return value;
})
}
query().then(value=>{
console.log(value);
})
setTimeout(()=>{
query().then(value=>{
console.log(value);
})
},1000)
Promise.reject
返回一个reject状态的promise
更改状态
new Promise((resolve,reject)=>{
resolve('fullfield');
}).then((value)=>{
if(value!=='success'){
// throw new Error('error');
return Promise.reject('rejectd')
}
}).catch(value=>console.log(value))
Promise.all
语法:Promise.all([ promise ]).then( ... )
全部promise状态均为resolved状态才可以then
const p1=new Promise(resolve=>{
setTimeout(()=> resolve('p1'),1000)
})
const p2=new Promise(resolve=>{
setTimeout(()=> resolve('p2'),2000)
})
Promise.all([p1,p2]).then(value=>console.log(value));
function ajax(){
return new Promise(resolve=>resolve());
}
function getUsers(names){
// 这里是一个promise数组
const promises= names.map(name=>{
return ajax(`xxx${name}`)
})
return Promise.all(promises);
}
getUsers(['user1','user2']).then(users=>console.log(users));
Promise.allSettled
语法结构和Promise.all一致,不同的是:
不管resolve还是reject都会接收,返回的结果为[ { status:xxx, value:xxx } ]
const p1=new Promise((resolve,reject)=>{
setTimeout(()=> reject('p1'),1000)
})
const p2=new Promise(resolve=>{
setTimeout(()=> resolve('p2'),2000)
})
Promise.allSettled([p1,p2]).then(value=>console.log(value));
Promise.race
哪个promise返回得最快,就获得哪个的结果
取返回的最快的那个promise的结果
let p1=new Promise(resolve=>{
setTimeout(()=>{
resolve('fullfield')
},1000);
})
let p2=new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('rejected')
},3000);
})
Promise.race([p1,p2]).then(value=>console.log(value))
.catch(value=>console.log(value));
做请求超时的例子
function ajax(){ /* ... */ }
function query(url,delay){
let promises=[
ajax(url),
new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('请求超时')
},delay)
})
]
return Promise.race(promises);
}
query('xxx',2000)
.then(value=>console.log(value))
.catch(value=>console.log(value));
Promise队列
队列的例子
在then中又返回了一个promise,就会形成一条链
队列中的每个成员都是一个promise,下一个成员是依赖于上一个成员的状态改变
let promise=new Promise(resolve=>{
setTimeout(()=>{
resolve('promise 1')
},1000)
})
promise=promise.then(value=>{
console.log(value);
return new Promise(resolve=>{
setTimeout(()=>{
resolve('promise 2')
},2000)
})
})
promise=promise.then(value=>{
console.log(value);
return new Promise(resolve=>{
setTimeout(()=>{
resolve('promise 3')
},2000)
})
})
promise.then(value=>{
console.log(value);
})
使用map封装promise队列
在then中返回promise时就会实现队列的操作:得等前面的完成再走后面的
把promise.then返回的promise赋给自己,依次往下进行传递
function queue(arr){
let promise=Promise.resolve()
arr.map((v,index)=>{
console.log(index);console.log(promise);
// promise.then返回的promise赋值给自己,依次往下进行传递
promise=promise.then(()=>{
return new Promise(resolve=>{
setTimeout(()=>{
console.log(v)
resolve()
},1000);
})
})
})
}
queue([1,2,3,4,5])
function queue(arr){
let promise=Promise.resolve()
arr.map((v)=>{
promise=promise.then(()=>{
// 这里也要加上return
return v();
})
})
}
function p1(){
return new Promise(resolve=>{
setTimeout(()=>{
console.log(1);
resolve()
},1000)
})
}
function p2(){
return new Promise(resolve=>{
setTimeout(()=>{
console.log(2);
resolve()
},1000)
})
}
queue([p1,p2])
使用reduce封装队列
function queue(arr){
let promise=Promise.resolve()
arr.reduce((promise,v)=>{
return promise.then(()=>{
return new Promise(resolve=>{
setTimeout(()=>{
console.log(v)
resolve()
},1000);
})
})
},promise)
}
queue([1,2,3,4,5])
扩展一下reduce的用法
let arr=['alice','angela','angela','tom'];
// 计算次数
const obj=arr.reduce((obj,cur)=>{
if(obj[cur]){
obj[cur]++;
}else{
obj[cur]=1;
}
return obj;
},{})
console.log(obj);
// 数组去重
const newArr=arr.reduce((newArr,cur)=>{
if(!newArr.includes(cur)){
newArr.push(cur);
}
return newArr;
},[])
console.log(newArr);
使用队列来渲染数据
class User{
render(users){
users.reduce((promise,user)=>{
return promise.then((resolve)=>{
return this.ajax('xxx',user)
}).then((user)=>{
// 这里user接收上一个promise resolve的user信息
return this.view(user)
})
},Promise.resolve())
}
ajax(url,name){
return new Promise((resolve,reject)=>{
const xhr=new XMLHttpRequest();
xhr.open('GET',url);
xhr.send();
xhr.onload=function(){
// if(this.status===200){
// resolve(name);
// }else{
// reject('加载失败')
// }
resolve(name);
}
xhr.onerror=function(){
reject(this);
}
})
}
view(user){
return new Promise(resolve=>{
setTimeout(()=>{
let h2=document.createElement('h2');
h2.innerHTML=user;
document.body.appendChild(h2);
resolve()
},1000)
})
}
}
new User().render(['Angela','Tom'])
async/await
基础语法部分
async:
-
async函数会默认返回一个promise对象(默认为resolved状态)
-
不管函数内部return的是什么,都会将其包装成promise对象
async function asyncFunc(){
return 'hello async';
}
console.log(asyncFunc());
asyncFunc().then(value=>console.log(value));
async function asyncFunc1(){
return new Promise(resolve=>{
setTimeout(()=>{
resolve(1)
},1000)
});
}
console.log(asyncFunc1());
asyncFunc1().then(value=>console.log(value));
JavaScript中的async/await详解 - #Empty - 博客园
JS中的async/await的用法和理解 - 曼施坦因 - 博客园
await:
-
提取promise返回的内容,相当于then,可以理解为then的语法糖。
当等待的是一个promise的时候,它会阻塞程序的执行;
-
当等待非promise时,则会按照同步程序返回值处理;
// await 关键字 只能放在 async 函数内部, await关键字的作用 就是获取 Promise中返回的内容, 获取的是Promise函数中resolve或者reject的值
// 如果await 后面并不是一个Promise的返回值,则会按照同步程序返回值处理,为undefined
const bbb = function(){ return 'string'}
async function funAsy() {
const a = await 1
console.log(a);
const b = await new Promise((resolve, reject)=>{
setTimeout(function(){
resolve('time')
}, 3000)
})
const c = await bbb()
console.log(a);
console.log(b);
console.log(c);
}
funAsy() // 运行结果是 3秒钟之后 ,输出 1, time , string,
async / await:
function ajax(url){
return new Promise((resolve,reject)=>{
const xhr=new XMLHttpRequest();
xhr.open('GET',url);
xhr.send();
xhr.onload=function(){
// if(this.status===200){
// resolve(JSON.parse(this.response));
// }else{
// reject('加载失败')
// }
resolve('fullfield');
}
xhr.onerror=function(){
reject(this);
}
})
}
async function get(user){
let id = await ajax('xxx');
let lessons = await ajax('xxx');
console.log(lessons);
}
get('Tom');
休眠函数例子
promise状态改变之前await会阻塞程序执行
function sleep(delay=2000){
return new Promise(resolve=>{
setTimeout(()=>{
resolve();
},delay)
})
}
async function show(){
for(const name of ['angela','tom']){
await sleep();
console.log(name);
}
}
show();
// let i=0;
// const arr=['angela','tom']
// do{
// await sleep();
// console.log(arr[i]);
// }while(i<arr.length)
进度条例子
function ajax(url){
return new Promise((resolve,reject)=>{
const xhr=new XMLHttpRequest();
xhr.open('GET','xxx');
xhr.send();
xhr.onload=function(){
// if(this.status===200){
// resolve(JSON.parse(this.response));
// }else{
// reject('加载失败')
// }
setTimeout(()=>{
resolve()
},1000)
}
xhr.onerror=function(){
reject(this);
}
})
}
async function getUsers(users){
console.log(users);
for(let i=0;i<users.length;i++){
await ajax()
let div=document.querySelector('.progress');
let progress=Math.round((i+1)/users.length*100)
console.log(progress);
div.style.width=progress+'%';
}
}
getUsers(['angela','tom','tonny','baby']);
class与async/await
如果一个类里面有then方法,会包装成promise
function ajax(url){
return new Promise((resolve,reject)=>{
const xhr=new XMLHttpRequest();
xhr.open('GET','xxx');
xhr.send();
xhr.onload=function(){
// if(this.status===200){
// resolve(JSON.parse(this.response));
// }else{
// reject('加载失败')
// }
setTimeout(()=>{
resolve('xmlhttprequest')
},1000)
}
xhr.onerror=function(){
reject(this);
}
})
}
class User{
constructor(name){
this.name=name;
}
then(resolve,reject){
// let promise = ajax('xxx');
// resolve(promise)
ajax('xxx').then(value=>resolve(value))
}
}
async function test(){
let user = await new User()
console.log(user);
}
test();
class Sample{
then(resolve,reject){
resolve('sample xxx')
}
}
async function print(){
let value=await new Sample();
console.log(value)
}
print();
封装在类内部的async函数
function ajax(url){
return new Promise((resolve,reject)=>{
const xhr=new XMLHttpRequest();
xhr.open('GET','xxx');
xhr.send();
xhr.onload=function(){
// if(this.status===200){
// resolve(JSON.parse(this.response));
// }else{
// reject('加载失败')
// }
setTimeout(()=>{
resolve('xmlhttprequest')
},1000)
}
xhr.onerror=function(){
reject(this);
}
})
}
class User{
async get(name){
let user = await ajax();
user += '-houdunren.com'
return user;
}
}
new User().get().then(value=>console.log(value));
用法总结
-
普通函数
-
对象
-
类
其实这3种是一种,只是换了表达形式而已
// 1.普通函数
async function test(){
// 这里要return一下。await拿到的是promise返回的结果
return await new Promise(resolve=>{
setTimeout(()=>{
resolve('abc')
},1000)
})
// console.log(value);
// return value;
}
test().then(value=>console.log(value))
// 2.对象中
let hd={
async test(){
// 这里要return一下。await拿到的是promise返回的结果
return await new Promise(resolve=>{
setTimeout(()=>{
resolve('abc')
},1000)
})
}
}
hd.test().then(value=>console.log(value))
// 3.类中实例的方法
class Hd{
async test(){
// 这里要return一下。await拿到的是promise返回的结果
return await new Promise(resolve=>{
setTimeout(()=>{
resolve('abc')
},1000)
})
}
}
new Hd().test().then(value=>console.log(value))
基本错误处理
async function test(){
// 这里要return一下。await拿到的是promise返回的结果
return await new Promise((resolve,reject)=>{
reject('error occurred');
setTimeout(()=>{
// resolve('abc')
},1000)
})
// console.log(value);
// return value;
}
// 因为async函数返回的是promise,所以错误处理同promies
test().then(value=>console.log(value)).catch(error=>console.log(error))
错误处理流程
-
async函数外部catch
-
函数内部try-catch代码块。若有多个await可以放到try-catch里面
发生错误时,后面的代码是不会执行的;
有错误都要处理
async function test(){
// 这里要return一下。await拿到的是promise返回的结果
return await new Promise((resolve,reject)=>{
reject('error occurred');
setTimeout(()=>{
// resolve('abc')
},1000)
})
// console.log(value);
// return value;
}
// 因为async函数返回的是promise,所以错误处理同promies
// test().then(value=>console.log(value)).catch(error=>console.log(error))
async function test1(){
try{
await new Promise((resolve,reject)=>{
reject('error occurred');
})
await new Promise((resolve,reject)=>{
reject('error reoccurred');
})
console.log('abc');
}catch(error){
console.log(error)
}
}
test1();
await并行
function h1(){
return new Promise(resolve=>{
setTimeout(()=>{
resolve('h1');
},2000)
})
}
function h2(){
return new Promise(resolve=>{
setTimeout(()=>{
resolve('h2');
},2000)
})
}
// promise.all方法
async function test(){
let res = await Promise.all([h1(),h2()])
console.log(res);
}
test();
// 一般方法
async function test1(){
let p1=h1();
let p2=h2();
let h1value=await p1;
let h2value=await p2;
console.log(h1value);
console.log(h2value);
}
test1();