阅读 604

一起拿下异步

拿下异步

写在前面

今天来回顾一下异步,在如今的前端。异步已经是非常重要的东西了,甚至可以说是不懂异步,什么都做不了了

今天从几个方面来总结

  • 总结一下常见的异步解决方案
  • 手写一下promise

异步经历了以下四个阶段

回调函数 —> Promise —> Generator —> async/await。

回顾一下开始容易感到困惑的问题

问:首先我们都清楚js是一个单线程的语言,单又可以开一个异步任务的线程是否与它的单线程执行模式自相矛盾呢?

答:js的执行时单线程的,但是浏览器是多线程的。即js的异步是由js执行线程和浏览器的事件触发线程等共同实现的(结合Electron开发的经验理解浏览中的多线程操作)。进程之间不太容易实现交流,线程可是没有这个忧虑的。js执行线程即遇到一个异步代码,会向浏览器请求援助。浏览器会新开一个线程处理,事件监听线程会监听处理的结果

步入正题(常见的异步解决方案)

1. 回调函数

直接看栗子(读取一个文本):

const fs = require("fs");
const path = require("path")


fs.readFile(path.join(__dirname, "./test.txt"), "utf8", (err, data) => {
    console.log(data);

})
复制代码

2. 发布订阅

写一个“信号中心”。当一个异步任务执行结束之后向内发布“信号”,则订阅者的回调就可开始执行(发布订阅模式)

class Event {
    constructor() {
        this.subs = [];
    }
    on(fn) {
        this.subs.push(fn)
    }
    emit(data) {
        this.subs.forEach(fn => fn(data));
    }
}

const fs = require("fs");
const path = require("path")
let e = new Event();

e.on((val) => {
    console.log(val);

})

fs.readFile(path.join(__dirname, "./test.txt"), "utf8", (err, data) => {
    if (err) return;
    e.emit(data);

})
复制代码

注意:有一些文档说发布订阅模式就是观察者模式,这是不对的。发布订阅更像是观察者的子集(日后总结设计模式的时候再展开吧)

3. 事件监听

这个东西,写个前端的都写过了

demo.onclick=function(){}
复制代码

4. promise

为了解决回调地狱的问题,promise出来了

const fs = require("fs");
const path = require("path")
const p = new Promise((resolve, reject) => {
    fs.readFile(path.join(__dirname, "./test.txt"), "utf8", (err, data) => {
        if (err) {
            reject(err);
        } else {
            resolve(data)
        }


    })
})
p.then(data => {
    console.log(data);

})
复制代码

注意promise的使用和原理是一个重点,笔者在下面会总结

5. generator+co

利用generator可以中断可以唤醒的执行机制。

为什么需要co?我们都知道generator是一个迭代器生成器,它调用一下才是一个迭代器,同时因为要唤醒我们也要不断则执行next()方法。这便很繁琐了,co就是为我们封装了这么一个自动执行的东西。

这里为了栗子的简便,笔者没有使用co 。主要要了解它做了什么

const fs = require("fs");
const path = require("path");
const ioPromise = new Promise((resolve, reject) => {
    fs.readFile(path.join(__dirname, "./test.txt"), "utf8", (err, data) => {
        if (err) {
            reject(err);
        } else {
            resolve(data)
        }
    })
});

function* read() {
    const data = yield ioPromise;
}
const it = read();
it.next().value.then(data => {
    console.log(data);
});
复制代码

6. async/awiat(generator+co的语法糖)

这个就是我们现在常用的啦

async function getData() {
    const res = await this.$aixos("...")
}
复制代码

第二阶段 promise的相关知识

1. 常用方法

原型上

  1. then():接收两个回调参数,后一个回调是可选参数,then(function(){...},function(){....})

    它的返回值需要注意一下(直接看mdn,这里很值得注意,因为手写时这里的逻辑很重要)

    • 返回了一个值,那么 then 返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
    • 没有返回任何值,那么 then 返回的 Promise 将会成为接受状态,并且该接受状态的回调函数的参数值为 undefined
    • 抛出一个错误,那么 then 返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
    • 返回一个已经是接受状态的 Promise,那么 then 返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
    • 返回一个已经是拒绝状态的 Promise,那么 then 返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
    • 返回一个未定状态(pending)的 Promise,那么 then 返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。
  2. catch():then(null,function(){})的别名。promise状态失败了走它。这里要注意的是在resolve之后的错误是不会捕获的,同时promise的错误具有“冒泡的性质”,它会一直向后传递直到被捕获。故建议将catch写到最后

    • 返回值:一个promise
  3. finally():不管promise的状态是成功还失败都走一下这个方法

因为then的返回值也均是promise,故为promise的链式调用提供了可能

静态方法

  • resolve: 返回一个状态由给定value决定的Promise对象。如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。通常而言,如果你不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该value以Promise对象形式使用。

  • reject: 返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法

  • all:参数(iterable)一般是元素是promise的数组,即[p1,p2,p3]。几个promise全resolve,all的返回值也是一个已完成的promise。有一个reject,则走失败

  • race:与all相似,[p1,p2,p3]哪一个promise最先状态改变它就认哪个。

2. 手写promise

**Promise** 对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。 这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的promise对象

一个 Promise有以下几种状态:

  • pending: 初始状态,既不是成功,也不是失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

手写之前,回想一下promise是怎么使用的。我们new的时候穿了一个执行函数,且当里面异步逻辑成功时执行resolve,失败时执行reject

即开始一个promise的状态是 pending,执行了resolve它就变成了fulfilled,又或者执行了reject它就变成了rejected

并且promise的状态是发生了改变便不会再变动了,成功就是成功。不会从成功态又转到失败态

1 来构造这个类(这个已经是最简版了)

const PENDING = "PENDING"; //运行态
const SUCCESS = "SUCCESS"; //成功态
const FUL = "FUL"; //失败态
class MyPromise {
    constructor(exector) {
            this.status = PENDING;
            this.res = undefined; //失败原因
            this.val = undefined; //成功值

            let resolve = (val) => {
                if (this.status != PENDING) return;
                this.val = val;
                this.status = SUCCESS;

            }
            let reject = (err) => {
                if (this.status != PENDING) return;
                this.res = err;
                this.status = FUL;

            }

            try {
                exector(resolve, reject);
            } catch (error) {
                reject(error);
            }
           
        }
        then(onfulfilled, omrejected) {
            if (this.status === SUCCESSS) {
                onfulfilled(this.val)
            }
            if (this.status === FUl) {
                omrejected(this.res);
            }
        
        }
    }


复制代码

2. 增加异步解决

上面的实现,若是一个异步任务。走到then时的状态还是PENDING走不通。

利用发布订阅模式,还是在异步结果出来之发布信号。then中实现订阅即可

class MyPromise {
    constructor(exector) {
        this.status = PENDING;
        this.res = undefined;
        this.val = undefined;
        //装then成功的回调
        this.onfulfilledCb = [];
        //装then失败的回调
        this.onrejectedCb = [];
        let resolve = (val) => {
            if (this.status != PENDING) return;
            this.val = val;
            this.status = SUCCESS;
            //成功结果出来执行已订阅的的回调
            this.onfulfilledCb.forEach(fn => fn(val))
        }
        let reject = (err) => {
            if (this.status != PENDING) return;
            this.res = err;
            this.status = FUL;
            //失败结果出来执行已订阅的的回调
            this.onrejectedCb.forEach(fn => fn(err));
        }

        try {
            exector(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }
    then(onfulfilled, onrejected) {


        if (this.status === SUCCESS) {
            onfulfilled(this.val);
        }
        if (this.status === FUL) {
            onrejected(this.res);

        }

        // 异步
        if (this.status === PENDING) {
            this.onfulfilledCb.push(onfulfilled);
            this.onrejectedCb.push(onrejected);
        }

    }
}
复制代码

3. 为了保证链式调用then必须是返回一个promise

注意上面常用方法中then的返回值类型,用setTimeout包一下是因为promise的规范是明确说明:then方法是异步执行的,故这里使用setTimeout模拟了一下

const resolvePromise = (promise2, x, resolve, reject) => {

    if (typeof x === "object" || typeof x === "function") {
        let then = x.then;
        // 有then方法默认它就是promise
        if (typeof then === "function") {
            then.call(x, y => {
                // 可能返回的还是一个promise,故递归处理
                resolvePromise(promise2, y, resolve, reject)
            }, z => {
                reject(z);
            })
        }
    } else {
        // 简单类型的值直接resolve出去
        resolve(x);
    }
}
const PENDING = "PENDING";
const SUCCESS = "SUCCESS";
const FUL = "FUL";
class MyPromise {
    constructor(exector) {
        this.status = PENDING;
        this.res = undefined;
        this.val = undefined;
        this.onfulfilledCb = [];
        this.onrejectedCb = [];
        let resolve = (val) => {
            if (this.status != PENDING) return;
            this.val = val;
            this.status = SUCCESS;
            this.onfulfilledCb.forEach(fn => fn())
        }
        let reject = (err) => {
            if (this.status != PENDING) return;
            this.res = err;
            this.status = FUL;
            this.onrejectedCb.forEach(fn => fn());
        }

        try {
            exector(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }
    then(onfulfilled, onrejected) {

        let promise2 = new MyPromise((resolve, reject) => {
            if (this.status === SUCCESS) {
                setTimeout(() => {
                    let x = onfulfilled(this.val);
                    resolvePromise(promise2, x, resolve, reject)
                }, 0)
            }
            if (this.status === FUL) {
                setTimeout(() => {
                    let x = onrejected(this.res);
                    resolvePromise(promise2, x, resolve, reject)
                }, 0)
            }

            // 异步
            if (this.status === PENDING) {
                this.onfulfilledCb.push(() => {
                    setTimeout(() => {
                        let x = onfulfilled(this.val);
                        resolvePromise(promise2, x, resolve, reject)
                    }, 0)
                });
                this.onrejectedCb.push(() => {
                    setTimeout(() => {
                        let x = onrejected(this.res);
                        resolvePromise(promise2, x, resolve, reject)
                    }, 0)
                });
            }
        })

        return promise2;
    }
}

复制代码

4 全部代码

const resolvePromise = (promise2, x, resolve, reject) => {

    if (typeof x === "object" || typeof x === "function") {
        let then = x.then;
        // 有then方法默认它就是promise
        if (typeof then === "function") {
            then.call(x, y => {
                // 可能返回的还是一个promise,故递归处理
                resolvePromise(promise2, y, resolve, reject)
            }, z => {
                reject(z);
            })
        }
    } else {
        // 简单类型的值直接resolve出去
        resolve(x);
    }
}
const PENDING = "PENDING";
const SUCCESS = "SUCCESS";
const FUL = "FUL";
class MyPromise {
    constructor(exector) {
        this.status = PENDING;
        this.res = undefined;
        this.val = undefined;
        this.onfulfilledCb = [];
        this.onrejectedCb = [];
        let resolve = (val) => {
            if (this.status != PENDING) return;
            this.val = val;
            this.status = SUCCESS;
            this.onfulfilledCb.forEach(fn => fn())
        }
        let reject = (err) => {
            if (this.status != PENDING) return;
            this.res = err;
            this.status = FUL;
            this.onrejectedCb.forEach(fn => fn());
        }

        try {
            exector(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }
    then(onfulfilled, onrejected) {

        let promise2 = new MyPromise((resolve, reject) => {
            if (this.status === SUCCESS) {
                setTimeout(() => {
                    let x = onfulfilled(this.val);
                    resolvePromise(promise2, x, resolve, reject)
                }, 0)
            }
            if (this.status === FUL) {
                setTimeout(() => {
                    let x = onrejected(this.res);
                    resolvePromise(promise2, x, resolve, reject)
                }, 0)
            }

            // 异步
            if (this.status === PENDING) {
                this.onfulfilledCb.push(() => {
                    setTimeout(() => {
                        let x = onfulfilled(this.val);
                        resolvePromise(promise2, x, resolve, reject)
                    }, 0)
                });
                this.onrejectedCb.push(() => {
                    setTimeout(() => {
                        let x = onrejected(this.res);
                        resolvePromise(promise2, x, resolve, reject)
                    }, 0)
                });
            }
        })

        return promise2;
    }
}


let p = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(1000)
    }, 1000)
});
p.then(data => {
    console.log(data);
    return new MyPromise((resolve, reject) => {
        setTimeout(() => {
            resolve(1000)
        }, 1000)
    })

}).then(data => {
    console.log(data);

})
复制代码

异步的问题可仍然没有结束,故本次的脑图总结放到下一次的博客中