JS异步编程 【javascript】

JS异步编程

异步初识

setTimeout 与 setInterval

定时回调进行中断执行

let x = 3;
setTimeout(() => {
    x = x + 3;
    console.log(x);
}, 1000);

循环中断 每隔1000ms就会执行一次回调函数

setInterval(() => {
    x = x + 3;
    console.log(x);
}, 1000);

老的异步方案 利用setTimeout解决

function double(value, callback) {
    setTimeout(() => callback(value * 2), 1000);
}
double(3, (x) => console.log(`I was given ${x}`));
console.log("我先输出");

异常处理

function double(value, success, failure) {
    setTimeout(() => {
        try {
            if (typeof value !== 'number') {
                throw 'value type is not number';
            }
            success(value * 2); //成功回调
        } catch (e) {
            failure(e); //错误回调
        }
    }, 1000);
}
double('1', (res) => {
    console.log("res", res);
}, (e) => {
    console.error(e);
});

回调地狱

嵌套异步回调 一个异步任务等待另一个异步任务的返回值,一个异步代码中执行另一部分异步代码时,可能会出现回调地狱,简单的说就是我们原来的异步解决方案就是通过回调函数进行异步操作,就会出现回调里面还有回调这样的情况,如果任务较多可能会出现回调嵌套很多次,也就是我们说的回调地狱。

function double(value, success, failure) {
    setTimeout(() => {
        try {
            if (typeof value !== 'number') {
                throw 'value type is not number';
            }
            success(value * 2); //成功回调
        } catch (e) {
            failure(e); //错误回调
        }
    }, 1000);
}
const successCallback = (x) => {
    double(x, (y) => console.log("y", y));
};
const failureCallback = (e) => console.error('e', e);
double(2, successCallback, failureCallback);

Promise

初识Promise

Promise是一种有三种状态的对象

  • 待定 pending
  • 兑现 fulfilled 也称解决resolved
  • 拒绝 rejected
//Promise 
let p = new Promise(() => {});
console.log(p);
setTimeout(console.log, 0, p);
//Promise是一个有状态的对象

//#通过执行函数控制Promise状态 
let p1 = new Promise((resolve, reject) => {
    resolve(); //把状态切换为兑现
});
console.log(p1);
//Promise {<fulfilled>: undefined}

let p2 = new Promise((resolve, reject) => {
    reject(); //把状态切换为拒绝
});
console.log(p2);
//Promise {<rejected>: undefined}

当p被输出时,Promise状态还为待定状态
因为还没有执行resolve或reject
Promise状态一旦改变则不可逆,继续修改状态会静默失败
Promise的状态只能改变一次

new Promise(() => setTimeout(console.log, 0, 'executor'));
setTimeout(console.log, 0, 'promise initialized');

let p = new Promise((resolve, reject) => setTimeout(resolve, 1000));
setTimeout(console.log, 0, p);

Promise.resolve

创建的Promise对象默认状态为待定
但Promise并非一开始必须为待定状态
可以通过Promise.resolve()静态方法实例化一个resolve状态的Promise

let p1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 1000);
});
let p2 = Promise.resolve();
console.log("p1", p1); //Promise {<pending>}
console.log("p2", p2); //Promise {<fulfilled>: undefined}
//Promise.resolve是一个静态方法 

Promise.resolve(any) 可以把任何参数变为一个Promise

console.log(Promise.resolve(2));
//Promise {<fulfilled>: 2}

如果参数是一个Promise 则返回原来的Promise

let p3 = Promise.resolve(3);
console.log(p3 === Promise.resolve(p3));
//true

能够包装任何非Promise值,包括Error Object,
可以将其转换为resolved的Promise

console.log(Promise.resolve(new Error('error to promise')));
/*
Promise {< fulfilled >: Error: error to promise}
*/

Promise.reject

`
实例化一个拒绝状态的Promise并抛出一个异步错误
`
let p1 = new Promise((resolve, reject) => {
    reject();
});
let p2 = Promise.reject();
console.log("p1", p1);
console.log("p2", p2);
`
二者是等价的,都是实例化了一个rejected状态的Promise
且二者都抛出了Uncaught Error
`
let p3 = Promise.reject(3);
console.log(p3); //Promise {<rejected>: 3}
p3.catch((e) => {
    console.log("e", e); //e
})
`
e 就是reject传入的参数
本质还是创建了一个Promise 调用了reject(3)
`
`
如果reject的参数是一个Promise 并不像resolve
它会将其变为一个reject的理由即变为catch的参数
`

let p4 = Promise.resolve('成功');
p4.then((res) => {
    console.log(res);
    return Promise.resolve('连起来');
}).then((res) => {
    console.log(res);
    return Promise.reject('结束吧');
}).then((res) => {
    console.log(res);
}).catch((e) => {
    console.log(e);
}).finally(() => {
    console.log("一切都结束了");
});

通俗理解Promise

Promise是一种对象、其有三种状态,resolve 表示解决 其传参可以被then传入的回调函数接收、reject 表示拒绝 其传参可以被 catch传入的回调函数接收。
利用Promise可以解决一定的回调地狱问题、使得代码维护性更高。

有趣的例子

const isPregnant = true;
const promise = new Promise((resolve, reject) => {
    if (isPregnant) {
        resolve('孩子他爹');
    } else {
        reject('老公');
    }
});
promise.then(name => {
    //resolve
    console.log('男人成为了' + name);
}).catch(name => {
    //reject
    console.log('男人成为了' + name);
}).finally(() => {
    console.log('男人和女人最终结婚了');
});

一个实际的例子

const imgPromise = (url) => {
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.src = url;
        img.onload = () => {
            resolve(img);
        }
        img.onerror = () => {
            reject(new Error('图片加载出现错误'));
        }
    });
}
imgPromise('https:/cdn.jim-nielsen.com/ios/512/netflix-2018-11-01.png').then(img => {
    document.body.appendChild(img);
}).catch(err => {
    const p = document.createElement('p');
    p.innerHTML = err;
    document.body.appendChild(p);
});
//可见利用Promise可以有效的解决回调地狱问题

实现Thenable接口

class MyThenable {
    then(res) {
        res();
    }
}
const able = new MyThenable();
able.then((res) => {
    console.log("HELLO WORLD");
});

Promise.prototype.then()

两个参数 一个onResolved回调 一个 onRejected回调
第二个与使用.catch()效果一样

let p1 = new Promise((resolve, reject) => {
    reject(new Error('none'));
});
let p2 = p1.then((res) => {
    console.log(res);
}, (e) => {
    console.log(e);
});
//.then()返回一个新的Promise p1!==p2
console.log(p2);

let p3 = Promise.resolve('resolve');
let p4 = p3.then(); //如果then没有参数则会保持p3的内容状态
console.log(p4); //但p4!==p3 但二者效果作用是一样的
p4.then((res) => {
    console.log(res);
})

then里面的回调函数的返回值是then返回Promise所使用resolve
时的参数,所以上一个.then里的返回值在紧接着的.then里可以接收到

Promise.resolve(1).then((res) => {
    console.log(res); //1
    return 2;
}).then((res) => {
    console.log(res); //2
    throw Error('none');
}).catch((e) => {
    console.log(e); //Error:none
});

Promise.prototype.catch()

只接受一个参数 onRejected处理程序

let p1 = new Promise((resolve, reject) => {
    reject('拒绝');
});
//返回新的Promise
let p2 = p1.catch((e) => {
    console.log(e);
});
console.log("p2", p2);
//可以继续使用p2 可以一致 then下去以及使用catch处理
let p4 = p2.then(() => {
    console.log("p1.catch resolve");
});

//新的Promise .catch 与 .then 行为一样
let p3 = p1.catch();
p3.catch((e) => {
    console.log("e", e);
});

Promise.prorotype.finally()

let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('finally');
        resolve('解决');
    }, 0);
});
let p3 = p2.finally(() => {
    console.log("onResolved 与 onRejected 都是执行");
    console.log("但不知道是resolve还是reject");
});
console.log("p3", p3);
p3.then(() => {
    console.log(".finally end");
});

非重入Promise方法

当Promise进行状态改变后、then catch 回调不一定立即执行
会将其放入执行队列 等待被执行
结果是Q F被输出执行
处理程序在同步代码没有执行完前不会被执行
then catch 中的处理程序并不会被同步执行

let p1 = Promise.resolve('res');
p1.then((res) => {
    console.log(res);
});
console.log('Q');
console.log('F');

临近处理程序的执行顺序

let p1 = Promise.resolve();
p1.then((res) => {
    console.log(1);
});
p1.then((res) => {
    console.log(2);
});
p1.finally(() => {
    console.log(3);
});
它们的执行顺序为代码顺序 1 2 3

reject 错误处理

//方法1
let p1 = new Promise((resolve, reject) => {
    reject(Error('none'));
});
p1.catch((e) => {
    console.log(e); //Error: none
});
//方法2
let p2 = new Promise((resolve, reject) => {
    throw Error('throw error');
});

p2.catch((e) => {
    console.log(e); //Error: throw error
});

这种方法异常处理是错误的,里面的异常并不被捕捉到

try {
    p1 = new Promise((resolve, reject) => {
        reject(Error('none'));
    })
} catch (e) {
    console.log(e);
}

Promise连锁

then catch finally 连起来使用

new Promise((resolve, reject) => {
    resolve('Q');
}).then((res) => {
    console.log(res);
}).catch((e) => {
    console.log(e);
}).then((res) => {
    console.log(res); //undefined
    // 因为是catch返回的新的Promise
    //在onResolved时并没有任何参数传入
});

如果有很多任务,进行同步,则可以实例化新的Promise
可以在处理程序中使用返回Promise的封装好的任务函数
可以有效的解决回调地狱问题
使用then catch 以及 finally 值观的进行编码

new Promise((resolve, reject) => {
    resolve('1');
}).then((res) => new Promise((resolve, reject) => {
    console.log(res);
    resolve('2');
})).then((res) => {
    console.log(res);
}).finally(() => {
    console.log('finally');
});

Promise 图

进行了一个二叉树的层级遍历
then catch finally有可以注册多个处理程序的特性

let A = new Promise((resolve, reject) => {
    console.log('A');
    resolve();
});
let B = A.then(() => {
    console.log("B");
});
let C = A.then(() => {
    console.log("C");
});
B.then(() => {
    console.log("D");
});
B.then(() => {
    console.log("E");
});
C.then(() => {
    console.log("F");
});
C.then(() => {
    console.log("G");
});

Promise.all()

即所有Promise状态都改变后程序进行执行

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('p1');
    }, 1000);
});
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('p2');
    }, 5000);
});
Promise.all([p1, p2]).then((ress) => {
    console.log(ress); // ['p1', 'p2']
});
//会等到p1 p2 状态都改变后执行

Promise.race()

返回状态最先改变的Promise

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('p1');
        resolve('p1');
    }, 1000);
});
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('p2');
        resolve('p2');
    }, 5000);
});
let p_race = Promise.race([p2, p1]);
p_race.then((res) => {
    console.log(res); //p1
    //因为p1的状态最先改变
});

串行Promise的合成

function add1(x) {
    return x + 1;
}

function add2(x) {
    return x + 2;
}

function add3(x) {
    return x + 3;
}
console.log(add1(add2(add3(0)))); //6

function addAll(x) {
    return Promise.resolve(x).then(add1).then(add2).then(add3);
}
addAll(0).then((res) => {
    console.log(res); //6
});

可以使用Array.prototype.reduce简化代码

function AddAllPro(x) {
    return [add1, add2, add3].reduce((promise, fn) => {
        return promise.then(fn); //给下一个Promise
    }, Promise.resolve(x));
    //reduce第二个参数为进行reduce原始值
    //比如求和 则设为0即可理解就好
}
AddAllPro(0).then((res) => {
    console.log(res); //6
});

继续compose函数的封装

function compose(...fns) {
    return (x) => fns.reduce((promise, fn) => {
        return promise.then(fn);
    }, Promise.resolve(x));
}
compose(add1, add2, add3)(0).then((res) => {
    console.log(res); //6
});

Promise 进行取消事件取消

由Kevin Smith提到的"取消令牌" (cancel token)

class CancelToken {
    constructor(cancelFn) {
        this.promise = new Promise((resolve, reject) => {
            console.log("任务");
            cancelFn(resolve, reject); //暴露resolve,reject
        });
    }
}
let resolve, reject;
let t1 = new CancelToken((resolve_, reject_) => {
    resolve = resolve_;
    reject = reject_;
    console.log(resolve);
    console.log(reject);
});
let p = t1.promise;
let id = setTimeout(() => {
    console.log("assign")
}, 1000);
p.finally(() => {
    clearTimeout(id);
});
resolve();
//可以随时取消console.log("assign")程序任务
console.log(p);

实现Promis.notify 进度通知

class TrackablePromise extends Promise {
    constructor(excutor) {
        const notifyHandlers = []; //存储需要通知的函数
        super((resolve, reject) => {
            excutor(resolve, reject, (info) => {
                notifyHandlers.forEach((handler) => {
                    handler(info);
                })
            })
        });
        this.notifyHandlers = notifyHandlers;
    }
    //添加事件
    notify(notifyHandler) {
        this.notifyHandlers.push(notifyHandler);
        return this;
    }
}

let p = new TrackablePromise((resolve, reject, notify) => {
    function countdown(x) {
        if (x > 0) {
            notify(`${20 * x} % remaining`);
            setTimeout(() => countdown(x - 1), 1000);
        } else {
            resolve();
        }
    }
    countdown(5);
});

p.notify((x) => console.log(x));
p.then((res) => console.log("end", res));
/*
80 % remaining
60 % remaining
40 % remaining
20 % remaining
end undefined
 */

异步函数

async

异步函数始终返回Promise对象

async function fn1() {
    for (let i = 0; i < 5; i++) {
        console.log(i);
    }
    return 'async fn1';
}
let p = fn1();
console.log(p); //Promise {<fulfilled>: 'async fn1'}
console.log('Q');

异常

async function fn2() {
    throw Error('error');
}
fn2().catch((e) => {
    console.log('fn2 error'); //fn2 error
});

当然也可以自定义返回Promise

async function fn3() {
    return Promise.reject(Error('none'));
}
let p3 = fn3();
p3.catch((e) => {
    console.log(e); //Error: none
});

await

await只能在async函数内使用
有时后异步任务完毕后才需要进行下一步操作
可以在async函数内使用await等待,以便任务一个一个执行
本质上await就是等待Promise的状态进行变化,否则将进行等待
await更像是then catch 的语法糖

async function fn4(x) {
    console.log(x);
    return Error('none');
}
async function fn5() {
    let p1 = await fn4(1);
    console.log(p1); //接收到reject的异常
    //当接收到返回的Promise后才会进行下面的任务,后则进行等待
    let p2 = await fn4(2);
    console.log('fn5 end');
}
fn5();
// console.log('Q');
// 1
// Q
// Error:none
// 2
// fn5 end

javscript在遇见await关键词时,会记录哪里暂停执行
等到await右边的值可用时,javascript运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行

async function fn6() {
    let fn = async () => {
        return await new Promise((resolve, reject) => {
            console.log('hello');
            resolve();
        });
    }
    await fn();
}
fn6(); //hello

异步函数策略

实现sleep函数

async function sleep(delay) {
    return new Promise((resolve) => {
        setTimeout(resolve, delay);
    })
}

async function fn1() {
    console.log('fn1 start');
    const t0 = Date.now();
    await sleep(1500);
    console.log(Date.now() - t0);
}
async function fn2() {
    console.log('fn2 start');
}
fn1();
fn2();
fn1 start
fn2 start
1505

平行执行

async function fn1(x) {
    await new Promise((resolve, reject) => {
        console.log(x);
        setTimeout(resolve, Math.random() * 1000);
    });
}
//如果几个异步任务没有顺序要求则可以先进行接收Promise
//然后再await处理
async function assign() {
    const p1 = fn1(1);
    const p2 = fn1(2);
    const p3 = fn1(3);
    await p1;
    await p2;
    await p3;
    console.log('assign over');
}
assign();
如果 await fn1(1); await fn1(2);await fn1(3);
则效率没有上面的要高一些

串行执行Promise

async function fn1(x) {
    return x + 1;
}
async function fn2(x) {
    return x + 2;
}
async function addAll(x) {
    for (const fn of [fn1, fn2]) {
        x = await fn(x);
    }
    return x;
}
addAll(0).then((res) => {
    console.log(res); //3
});

栈追踪与内存管理

Promise与异步函数的功能有相当程度的重叠,但内存中二者差别很大
简单知道就好

function f1(resolve, reject) {
    setTimeout(resolve, 1000, 'Q');
    console.log('f1');
}

function f() {
    new Promise(f1);
}
f();
//调用栈顺序
//setTimeout=>setTimeout(async)=>f1=>f

//使用async
async function fasync() {
    await new Promise(f1);
}
fasync();
//调用栈顺序 fasync => async function(async) => fasync
```js


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高万禄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值