Js-Promise异步编程

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 resolve

13、其他类型的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方法都依赖于上一个promise

28、使用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版权协议,转载请附上原文出处链接和本声明。