1、什么是异步编程
// 开启一个多线程
// 如果网络请求很慢,那么就会先处理这个任务
// 然后遇到io阻塞的时候去接着处理
// 类似于开启了一个多线程,
// 举个例子:家里的洗衣服,做饭,打扫卫生
// 我们不能一直等洗衣机洗完衣服然后再去做饭
// 所以这时就开始了多线程
2、异步加载图片初体验
function loadImg(url, resolve, reject) {
// 创建图片的对象
let img = new Image();
// 设置图片的路径
img.src = url;
// 绑定回调函数
img.onload = () => {
resolve(img)
};
img.onerror = reject;
}
loadImg('./img.png', img => {
document.body.append(img);
console.log('加载成功');
}, () => {
console.log('加载失败')
})
console.log('主线程继执行')
/*
主线程继执行
加载成功
*/
3、定时任务轮询
function interval(callback, delay = 1000) {
let id = setInterval(() => callback(id), delay);
}
my_div = document.createElement('div')
my_div.style.background = 'green';
my_div.style.width = my_div.style.height = 200 + 'px';
my_div.style.position = 'absolute';
my_div.style.left = 0 + 'px';
document.body.appendChild(my_div)
interval((time_id) => {
const left = parseInt(window.getComputedStyle(my_div).left);
my_div.style.left = left + 10 + 'px';
console.log(left);
// 如果当前的位置是大于250px的话
if (left >= 250){
// 将上一个任务清空
clearInterval(time_id);
// 创建一个新的任务,让他变窄
interval(time_id=>{
const width = parseInt(window.getComputedStyle(my_div).width);
my_div.style.width = width -10 + 'px';
if (width <= 20){
clearInterval(time_id);
}
})
}
})
console.log('主线程任务完成')
console.log('最后到任务队列中寻找新的任务')
console.log('只有主线程中代码执行完才会去任务队列中寻找新的任务')
4、通过文件依赖了解定时任务
function load(script_, resolve) {
let script = document.createElement('script');
script.innerHTML = script_;
script.onload = resolve;
document.body.appendChild(script);
}
my_script = "function inner(){\n" +
" console.log('inner !')\n" +
"}"
load(my_script, () => {
inner();
})
console.log('永远在主线程之后才开始事件循环')
console.log('加载的任务加入队列是随机的,看谁先加入任务队列')
console.log('如果有两个模块是依赖的关系,有先后的加载顺序')
console.log('可以将模块放到另一个模块里面,就形成了一个嵌套函数')
console.log('可以将任务队列理解为一个栈')
5、ajax异步任务请求管理
function ajax(url, callback) {
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
xhr.onload = function () {
if (this.status === 200) {
callback(JSON.parse(this.response));
} else {
throw new Error('加载失败');
}
}
}
ajax('https://www.baidu.com', (res)=>{
console.log('请求成功');
// 再次发送一个异步请求
ajax('https://...' + res, res_=>{
console.log('第二次异步请求')
})
})
/*
先会发送一个http的请求
然后将回调函数添加到任务列表
当检测到可以执行这个任务的时候
就会将这个任务拿到主线程里面来执行
有一个好处就是之前在主线程里面创建的变量
现在可以拿来使用(顶级作用域)
*/
6、promise微任务处理机制
// pending 准备阶段:Promise {<pending>}
console.log(new Promise((resolve, reject) => {
}))
// fulfilled 成功状态: Promise {<fulfilled>: '成功状态'}
console.log(new Promise((resolve, reject) => {
resolve('成功状态')
}))
// rejected 失败状态 : Promise {<rejected>: '失败状态'}
console.log(new Promise((resolve, reject) => {
reject('失败状态')
}))
new Promise(((resolve, reject) => {
resolve('成功')
})).then(
value => {
console.log(value);
console.log('处理成功的回调函数');
},
reason => {
console.log(reason);
console.log('处理失败的回调函数');
}
).then(
value => {
console.log('第二个成功处理');
},
reason => {
console.log('第二个微任务处理失败')
}
)
/*
promise 微任务队列
会一直轮询任务,微任务队列优先
成功的时候就会按顺序一直执行微任务
(大人的小孩,要先照顾小孩)
(前面说的栈是宏任务队列)
*/
7、宏任务与微任务的执行顺序
// 将任务添加到宏任务队列
setTimeout(() => {
// 这里是添加到宏任务队列,等待main thread执行完后才处理
console.log('set time out in stack')
})
new Promise(resolve => {
// 在这里可以理解为代码和主线程是同步执行的, 表示创建准备的过程
console.log('creating promise in main thread')
// 将任务加到微任务队列
resolve();
}).then(value => {
// 将任务添加到微任务队列,
console.log('then method in micro stack')
})
// 主线程这里执行完后才会执行其他任务
// 如果有微任务队列,那么先执行微任务
// 最后才执行宏任务队列
console.log('main thread')
/*
执行主线程
creating promise in main thread
main thread
执行微任务队列
then method in micro stack
执行宏任务队列
set time out in stack
*/
8、将微任务提升到宏任务
let promise = new Promise(resolve => {
// 这里是一个宏任务
setTimeout(()=>{
console.log('set time out in stack');
// 这是设置了一个微任务
resolve('micro task');
}, 0);
console.log('create promise');
})
promise.then(value => {
console.log('成功', value);
})
console.log('main thread');
/*
在这里微任务是在宏任务的执行过程中创建的
也就是在执行宏任务之前,微任务列表里面是没有任务的
只有相关的宏任务执行完毕之后才会有微任务
所以这里的微任务是最后才执行的
*/
/*
create promise
main thread
set time out in stack
成功 micro task
*/9、promise单一状态与状态中转
let p1 = new Promise(((resolve, reject) => {
setTimeout(()=>{
resolve('成功')
}, 2000)
}))
new Promise((resolve, reject) => {
// 状态的改变是不可逆的,是单向的
// 也就是 resolve 之后再 reject 是不会有任何效果的
// 如果resolve传入的是另一个promise对象,那么就会等待这个对象返回的状态
// 此时传入的是p1的状态,就是resolve里面传入的值
// 这里就相当于状态中转移
resolve(p1);
// setTimeout(() => {
// resolve('成功')
// }, 2000)
}).then(value => {
console.log(value)
},
reason => {
console.log(reason)
})
console.log('main thread')10、promise.then的基本语法
new Promise(((resolve, reject) => {
// resolve('成功买了一瓶可乐');
reject('涨价了,买不起');
})).then(null, reason => {
console.log('处理了失败的状态' ,reason);
})
// 如果值关注于某一状态,那么回调函数可以用 undefined 或者 null 来占位11、promise.then的返回值也是promise
let p1 = new Promise(((resolve, reject) => {
resolve('fulfilled');
}))
let p2 = p1.then(
value => {
console.log(value)
},
reason => {
console.log(reason)
}
)
console.log(p1)
console.log(p2)
/*
新开的promise只是添加到了微任务里面
所以 p2 打印出来还处于微任务里面的
所以第一次遍历微任务的时候只是处理了第一次的promise
并没有处理接下来的返回的新的promise
此时的promise处于准备状态
新的promise只有第二次遍历微任务列表的时候才会执行
*/
setTimeout(()=>{
console.log(p1);
console.log(p2);
})
/*
由于微任务的状态是早于宏任务的
于是在微任务执行完之前是不会执宏任务的
所以在这里微任务一共遍历了两次微任务列表
执行完后才会执行宏任务
于是打印的两个都是fulfilled状态
*/
/*
所以每一个then方法都是对上一个promise的处理
然后如果上一个promise处理是成功的,那么接下来的promise也是成功的
否则就会一直失败
*/12、then的返回值处理技巧
let p1 = new Promise(((resolve, reject) => {
resolve('first resolve');
})).then(
// 会默认处理成 resolve() 新的 promise成功对象
// value => 'return from first promise: ' + value,
// 这一句话和上一句是等价的
// value => new Promise(resolve => resolve('return from first promise: ' + value)),
// 如果返回的promise处理的时间比较长,那么还是要等待的
// 通过下面这一句话,说明最后还是要等待两秒钟才会出结果的
value => {
return new Promise(resolve => {
setTimeout(()=>{
resolve('return from first promise: ' + value);
}, 2000)
})
},
// 如果返回的是一个 throw new Error('error')
// 那么接下来就会捕获异常,自动封装为 promise reject 对象
// 不用手动去创建,当然,手动创建promise reject对象也是可以的
reason => console.log(reason),
).then(
value => console.log('in the second ->' + value)
)
// 核心,后面的then是对前面返回的promise的处理
// 两秒之后才会打印
// in the second ->return from first promise: first resolve13、其他类型的promise封装
let p1 = new Promise(((resolve, reject) => {
resolve('fulfilled');
})).then(
value => {
// 返回一个普通的对象
// return {
// name: 'alex:返回的就是一个普通的对象',
// }
// 返回的不是一个普通的对象, 而是封装的一个promise
// 拥有和配普通promise的处理方法是一模一样的
// 下面value拿到的值就是 onfulfilled() 里面的值
// 说白了拿到的就是一个字符串
return{
then(onfulfilled, onrejected) {
onfulfilled('这是对象');
}
}
},
reason => {
}
).then(value => {
console.log(value);
})14、使用promise封装异步请求
function ajax(url) {
return new Promise(((resolve, reject) => {
let 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('https://me/login').then(value => {
return ajax('https://me/get_course?id' + value.user.id);
}).then(value => {
console.log(value);
})15、promise多种错误与catch检测
new Promise(((resolve, reject) => {
reject('error')
})).then(value => {
console.log(value);
}).catch(reason => {
console.log(reason);
})16、自定义错误处理
class ParamError extends Error {
constructor(msg) {
super(msg);
this.name = 'ParamError'
}
}
class HttpRequestError extends Error {
constructor(msg) {
super(msg);
this.name = '网络请求错误'
}
}
// 同步执行代码才可以使用 throw 方法抛出错误
// 否则异步的代码就只能使用 凡是有 onload 等的几乎都是异步的
// reject(new Error('error'))
// 的方法抛出错误才可以
new Promise(((resolve, reject) => {
// throw new ParamError('请求类型错误');
throw new HttpRequestError('请求类型错误');
})).then(value => {
console.log(value);
}).catch(reason => {
if (reason instanceof ParamError) {
console.log(reason.message);
}
if (reason instanceof HttpRequestError) {
alert(reason.message);
}
})
17、使用finally实现异步加载动画
const div = document.createElement('div');
div.style.cssText = 'width: 100px; height:100px; background:red;color:white; display:none';
document.body.appendChild(div);
const promise = new Promise(((resolve, reject) => {
div.style.display = 'block';
reject('reject');
}))
.then(
value => {
console.log('resolve')
}
)
.catch(reason => {
console.log('reason')
})
.finally(()=>{
div.style.display = 'none';
console.log('永远会执行')
})18、异步加载图片
function load_img(src){
return new Promise(((resolve, reject) => {
const img = new Image();
img.src = src;
img.onload = ()=>{
resolve(img);
};
img.onerror = reject;
document.body.appendChild(img);
}))
}
load_img('url').then(img=>{
img.style.border = 'solid 1px red'
})19、定时器的封装
function timeout(delay=1000){
return new Promise(resolve => {
setTimeout(resolve, delay);
})
}
timeout(2000).then(() => {
console.log('first output 2s');
return timeout(2000);
}).then(()=>{
console.log('second timeout 4s')
})
20、扁平化promise
const div = document.createElement('div');
div.style.cssText = 'width: 100px; height:100px; background:red;color:white;position:absolute';
document.body.appendChild(div);
function interval(delay = 1000, callback) {
return new Promise(resolve => {
let id = setInterval(() => {
callback(id, resolve);
}, delay);
})
}
interval(100, (id, resolve) => {
// console.log('call back');
// clearInterval(id);
let left = parseInt(window.getComputedStyle(div).left);
div.style.left = left + 10 + 'px';
// resolve();
if(left >= 250){
clearInterval(id);
resolve(div);
}
}).then(div => {
return interval(100, (id, resolve)=>{
let width = parseInt(window.getComputedStyle(div).width);
div.style.width = width - 10 + 'px';
if (width <= 40){
clearInterval(id);
resolve(div);
}
})
}).then(div => {
div.style.backgroundColor = 'green';
})
21、promise封装script加载脚本
function load_script(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_script('path to script 1').then(script => {
console.log(script);
return load_script('path to script 2');
}).then(script2 => {
console.log(script2)
})
22、promise.resolve缓存后台数据
// Promise.resolve('resolve').then(value => {
// console.log(value);
// })
// 通过这个实现前台的数据短时间不要频繁的请求用户,
// 而是利用前台的缓存数据,这样不但可以减少后台服务器的压力
// 而且还可以提高用户界面刷新的效率
// 函数本身也是一个对象
// 所以也可以向函数中添加属性
// function db() {}
// db.site = 'https://this/is/a/site.html'
// console.log(db.site)
// console.log(db)
// 所以可以向函数对象中添加一个属性来保存数据
function ajax(url) {
return new Promise(((resolve, reject) => {
let 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);
}
}))
}
function query(name) {
// 定义一个缓存
// || 如果左是一个false,那么就会执行右边的语句
// && 左侧为假值就会返回false
// 创建了一个缓存区域
const cache = query.cache || (query.cache = new Map())
if (cache.get(name)){
console.log('走缓存了');
console.log(cache.get(name));
}
return ajax('url+' + name).then(value => {
cache.set(name, 'url-data');
console.log('没有走缓存');
console.log(value);
return value;
})
}
// 查询某一个用户的数据
query('username').then(user => {
console.log(user);
})
23、promise.reject的使用
// 封装promise对象
let hd = {
then(onfulfilled, onrejected) {
onfulfilled('完成');
}
}
Promise.resolve(hd).then(value => {
console.log(value);
})
// 直接reject对象
Promise.reject('fail').then(value => {
}).catch(reason => {
console.log(reason);
})
new Promise(((resolve, reject) => {
resolve('from first');
})).then(value => {
console.log('then...');
// throw new Error('error');
return Promise.reject('error');
}).catch(reason => {
console.log(reason);
})
24、promise.all批量处理promise
const p1 = new Promise(((resolve, reject) => {
setTimeout(()=>{
resolve('first promise');
}, 100)
}))
const p2 = new Promise(((resolve, reject) => {
setTimeout(()=>{
resolve('second promise');
}, 100)
}))
// p3本身是一个新的promise对象,此时表示的是一个成功的状态
const p3 = new Promise(((resolve, reject) => {
reject('fail');
})).catch(reason => {
console.log(reason);
// 虽然是成功状态,但是没有返回值,所以是undefined
})
// Promise.all([...]), 批处理
// 如果某一个是失败的,那么就表示整体是失败的
Promise.all([p1, p2, p3]).then(value => {
console.log(value);
})
// ['first promise', 'second promise', undefined]
// 需求:根据用户名来批量获取数据
function ajax(url) {
return new Promise(((resolve, reject) => {
let 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);
}
}))
}
// 创建一个用户请求promise列表
// const promises = ['u1', 'u2', 'u3'].map(value => {
// return ajax('https://get/user/info?user=' + value);
// })
// 在这里可以封装成一个函数
function get_users(users){
const ps = users.map(value => ajax('https://get/user/info?user=' + value));
return Promise.all(ps);
}
get_users(['u1', 'u2', 'u3']).then(value => {
console.log(value);
})
25、promise.allsettled使用方法
const p1 = new Promise(((resolve, reject) => {
reject('reject');
}));
const p2 = new Promise(((resolve, reject) => {
resolve('resolve');
}))
// 这个接口始终都是已经解决的状态
// 以 [{}, {}, ...] 的形式返回
Promise.allSettled([p1, p2]).then(value => {
console.log(value);
})
// [
// {status: 'rejected', reason: 'reject'}
// {status: 'fulfilled', value: 'resolve'}
// ]
26、promise.race使用方法
const p1 = new Promise(((resolve, reject) => {
setTimeout(()=>{
resolve('resolve 1');
}, 2000);
}));
const p2 = new Promise(((resolve, reject) => {
setTimeout(()=>{
resolve('resolve 2');
}, 1000);
// 可以设置请求超时
// 如果将settimeout里面的resolve设置成reject,然后正常发送网络请求
// 如果在规定的时间里面resolve那么就表示请求成功
// 否则定时器将会触发reject,表示请求超时
}))
Promise.race([p1, p2]).then(value => {
console.log('会返回最先完成的,其他的将不会返回');
console.log(value);
}).catch(reason => {
console.log(reason);
})
// race 本身就有一个比赛的意思
// 这里比的就是看谁先完成请求,用的时间最短
27、promise队列原理
let promise = Promise.resolve('resolve');
promise.then(value => {
console.log(value);
// 队列操作
return new Promise((resolve => {
setTimeout(()=>{
resolve('link');
}, 500);
}))
}).then(value => {
console.log('second ' + value);
})
// resolve
// second link
// 原理:队列中的每一个成员都是一个promise
// 每一个then方法都依赖于上一个promise28、使用map实现promise队列
function queue(num){
let promise = Promise.resolve();
num.map(value => {
promise = promise.then(_ => {
return new Promise(resolve => {
setTimeout(()=>{
console.log(value);
resolve();
}, 1000)
})
})
})
}
// 每一个数字都会等待一秒钟以后输出
// 每一个promise都会等待前面的promise完成
queue([1, 2, 3, 4]);29、reduce封装promise队列
function queue(num){
num.reduce((promise, n)=>{
return promise.then(_=>{
return new Promise(resolve => {
setTimeout(()=>{
console.log(n);
resolve();
}, 1000);
});
});
}, Promise.resolve());
}
// reduce 基本用法
// array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
queue([1, 2, 3, 4]);30、使用队列渲染数据
class User {
render(users) {
users.reduce((promise, user) => {
return promise.then(_ => {
return this.ajax(user);
// 当请求执行成功的时候,渲染下面这一行代码
// 返回的是一个promise对象, 拿到用户数据,渲染视图
}).then(user=>{
return this.view(user);
// 然后可以做其他的事情
}).then(value => {
console.log(value);
})
}, Promise.resolve());
}
// 渲染视图函数
view(user) {
return new Promise(resolve => {
console.log(user);
let h3 = document.createElement('h3');
h3.innerHTML = user;
document.body.appendChild(h3);
resolve('做其他的事情');
})
}
// 网络请求
ajax(url) {
return new Promise(resolve => {
// 模拟请求拿到user的数据,然后解析出来
let user = 'user'
resolve(user);
})
}
}
let u = new User();
u.render(['alex', 'tom']);31、async和await语法糖
// 就是Promise的语法糖
async function func(){
return 'result'
}
// Promise {<fulfilled>: 'result'}
// 返回的就是一个已经解决的promise状态
// console.log(func())
func().then(value => {
console.log(value);
// ------> result
})
// 相当于以下写法
async function func1(){
return new Promise(resolve => {
setTimeout(()=>{
resolve('resolved');
}, 500);
})
}
func1().then(value => {
console.log(value);
})
// await 的写法就相当于then的写法
async function await_() {
let value = await func1();
console.log(value);
let new_promise = await new Promise((resolve => {
setTimeout(()=>{
resolve('new_promise');
}, 1000);
}))
console.log(new_promise)
return 'await_ promise done';
}
await_().then(value => {
console.log(value);
});
32、基于async和await实现异步请求
function ajax(url) {
return new Promise(((resolve, reject) => {
let 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);
}
}))
}
async function get(user){
let user_info = await ajax('https://get/user/info?user=' + user);
let user_course = await ajax('https//get/user/course?courseId=' + user_info.id);
console.log(user_course);
}
get('username');33、async实现延时函数
async function sleep(delay = 1000){
return new Promise(resolve => {
setTimeout(()=>{
resolve();
}, delay)
})
}
async function show(){
for(const u of ['alex', 'tom', 'jhon']){
await sleep();
console.log(u);
}
}
show();34、await实现进度条
const loading = document.createElement('div');
loading.style.cssText = 'height:50px;background: #8e44ad;display:flex;justify-content:center;align-items:center;font-size:30px;color:#fff';
loading.innerHTML = '0%';
document.body.appendChild(loading);
async function sleep(delay=1000){
return new Promise(resolve => {
setTimeout(()=>{
resolve();
}, delay);
})
}
(async function get_users_info(users){
for (let i = 0; i < users.length; i++){
// 模拟网络请求拿到用户信息
await sleep();
// 计算进度条的比例
let process = ((i +1) / users.length + '').substr(0, 4);
loading.innerHTML = 100 * process + '%';
}
})(['user1', 'user2', 'user3']);
35、await并行执行技巧
function p1(){
return new Promise(resolve => {
setTimeout(()=>{
resolve('promise1');
}, 1000);
})
}
function p2(){
return new Promise(resolve => {
setTimeout(()=>{
resolve('promise2');
}, 1000);
})
}
// 异步代码同步化
// async function main(){
// let h1 = await p1();
// console.log(h1);
// let h2 = await p2();
// console.log(h2);
// }
// 并行执行, await是当作then来使用的
async function main(){
// 利用内部的方法
// let res = Promise.all([p1(), p2()]);
// console.log(res);
// promise 在启动的时候就会立即执行
let h1 = p1();
console.log(h1);
// s所以这两个是同时开始执行的
let h2 = p2();
console.log(h2);
setTimeout(()=>{
console.log(h1, h2);
}, 1000)
}
main();版权声明:本文为qq_52827902原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。