仿co自动执行函数

写在前面

最近发现了一个叫co的神奇东西,似乎是es7没发布时,没有async,await时的一个非常好用的库,感觉挺好玩的,打算模拟实现一个。

实现过程

只能执行同步代码版

这一版的代码十分简单,主要做到三点就行了

  1. 把返回的promise的resolve函数一层层往下传,当迭代结束时,把生成器函数的返回值传给resolve函数
  2. 如果没有结束迭代,就把上一次迭代获得的值作为下一次next函数执行的参数传入,一直重复执行直到迭代结束为止


function run(generatorFunc, data) {
    // 获得生成器
    const generator = generatorFunc(data);
    // 执行next方法获取结果
    let result = execute(generator);
    return new Promise((resolve, reject) => {
        // 处理结果
        handleResult(generator, result, resolve, reject);
    });
}
function handleResult(generator, result, resolve, reject) {
    // 如果result.done是真,证明已经迭代完了,而且第一次为true时的value是生成器函数的返回值
    // 所以我们通过resolve可以把返回值传给run函数外的then
    if (result.done){
        resolve(result.value);
        return;
    }
    // 如果没有结束, 就继续执行, 同时把上一个next返回的value传回去
    result = execute(generator, result.value);
    // 这里的generator, resolve, reject都是一层一层往下传不会变的
    // 因为要执行的是同一个生成器, 要接收返回值和错误原因的也是同一个函数, 都是要传给run函数返回的promise绑定的then
    handleResult(generator, result, resolve, reject);
}
// 就是执行next方法
function execute(generator, data) {
    return generator.next(data);
}


function run(generatorFunc, data) {
    // 获得生成器
    const generator = generatorFunc(data);
    // 执行next方法获取结果
    let result = execute(generator);
    return new Promise((resolve, reject) => {
        // 处理结果
        handleResult(generator, result, resolve, reject);
    });
}
function handleResult(generator, result, resolve, reject) {
    // 如果result.done是真,证明已经迭代完了,而且第一次为true时的value是生成器函数的返回值
    // 所以我们通过resolve可以把返回值传给run函数外的then
    if (result.done){
        resolve(result.value);
        return;
    }
    // 如果没有结束, 就继续执行, 同时把上一个next返回的value传回去
    result = execute(generator, result.value);
    // 这里的generator, resolve, reject都是一层一层往下传不会变的
    // 因为要执行的是同一个生成器, 要接收返回值和错误原因的也是同一个函数, 都是要传给run函数返回的promise绑定的then
    handleResult(generator, result, resolve, reject);
}
// 就是执行next方法
function execute(generator, data) {
    return generator.next(data);
}


测试代码


run(function * () {
    let name = yield "sena";
    let age = yield 16;
    return {
        name,
        age
    }
}).then((data) => {
    console.log(data);    //  { name: 'sena', age: 16 }
}, (err) => {
    console.log("error:",err);
});


能执行异步代码版

这次的代码也不难,主要是增加了处理执行next方法时返回值是一个promise的逻辑,大致思路是,把执行下次next的代码放到返回promise的then中,也就是当返回的promise执行了resolve方法,才会触发then中绑定的generator.next()方法



function run(generatorFunc, data) {
    // 获得生成器
    const generator = generatorFunc(data);
    // 执行next方法获取结果
    let result = execute(generator);
    return new Promise((resolve, reject) => {
        // 处理结果
        handleResult(generator, result, resolve, reject);
    });
}
function handleResult(generator, result, resolve, reject) {
    // 如果result.done是真,证明已经迭代完了,而且第一次为true时的value是生成器函数的返回值
    // 通过resolve可以把返回值传给run函数外的then
    if (result.done){
        resolve(result.value);
        return;
    }
    // 如果值是个promise
    if (isPromise(result.value)){
        // 把next()放到then里面执行
        result.value.then((data) => {
            result = execute(generator, data);
            handleResult(generator, result, resolve, reject);
        })
    } else {
        // 如果没有结束, 就继续执行, 同时把上一个next返回的value传回去
        result = execute(generator, result.value);
        // 这里的generator, resolve, reject都是一层一层往下传不会变的
        // 因为要执行的是同一个生成器, 要接收返回值和错误原因的也是同一个函数, 都是要传给run函数返回的promise绑定的then
        handleResult(generator, result, resolve, reject);
    }
}
// 就是执行next方法
function execute(generator, data) {
    return generator.next(data);
}
// 用于判断返回值是不是promise
function isPromise(obj) {
    return 'function' == typeof obj.then;
}

测试代码



function * requestData() {
    let name = yield api1();
    console.log(name);      // 1s后输入 "sena"
    let age = yield api2();
    console.log(age);       // 2s后输出 16
    return {
        name,
        age
    };
}
function api1() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("sena");
        }, 1000);
    })
}
function api2() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(16);
        }, 1000);
    })
}
run(requestData).then((res) => {
    console.log(res);    // 2s后输出{ name: 'sena', age: 16 }
});

加入错误处理

到这里代码就开始有意思起来了,主要是实现下面两点

  1. 用try catch捕获异步的异常
  2. 如果没有try catch就执行reject方法

这里我们会用到generator.throw方法, 用于把reject的情况转化成error传给生成器内的try catch


function run(generatorFunc, data) {
    // 获得生成器
    const generator = generatorFunc(data);
    // 执行next方法获取结果
    let result = execute(generator);
    return new Promise((resolve, reject) => {
        // 处理结果
        handleResult(generator, result, resolve, reject);
    });
}
function handleResult(generator, result, resolve, reject) {
    // 如果result.done是真,证明已经迭代完了,而且第一次为true时的value是生成器函数的返回值
    // 通过resolve可以把返回值传给run函数外的then
    if (result.done){
        resolve(result.value);
        return;
    }
    // 如果值是个promise
    if (isPromise(result.value)){
        // 把next()放到then里面执行
        result.value.then((data) => {
            result = execute(generator, data);
            handleResult(generator, result, resolve, reject);
        }, (err) =>
        // 当返回的promise执行reject时
        {
            // 在外面包一层try catch, 如果生成器函数里没有写try catch, 就会在这里捕获error
            try {
                // 把错误抛入生成器
                result = throwError(generator, err);
                // 如果错了try catch就继续处理
                handleResult(generator, result, resolve, reject);
            }catch (e) {
                // 如果在这里接收到错误, 证明生成器里的错误没有被捕获, 执行绑定的reject
                reject(e);
            }
        })
    } else {
        // 如果没有结束, 就继续执行, 同时把上一个next返回的value传回去
        result = execute(generator, result.value);
        // 这里的generator, resolve, reject都是一层一层往下传不会变的
        // 因为要执行的是同一个生成器, 要接收返回值和错误原因的也是同一个函数, 都是要传给run函数返回的promise绑定的then
        handleResult(generator, result, resolve, reject);
    }
}
// 就是执行next方法
function execute(generator, data) {
    return generator.next(data);
}
// 用于把错误抛入生成器
function throwError(generator, error) {
    return generator.throw(error);
}
// 用于判断返回值是不是promise
function isPromise(obj) {
    return 'function' == typeof obj.then;
}


测试代码



function * requestData() {
    let name = yield api1();
    console.log(name);      // 1s后输入 "sena"
    let age = yield api2();
    console.log(age);       
    return {
        name,
        age
    };
}
function api1() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("sena");
        }, 1000);
    })
}
function api2() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject("api2炸了");
        }, 1000);
    })
}
run(requestData).then((res) => {
    console.log(res);
}, (err) => {
    console.log("错误原因 : " + err);    // 错误原因 : api2炸了
});

还是测试代码


function * requestData() {
    let name = yield api1();
    console.log(name);      // 1s后输入 "sena"
    let age;
    try {
        age = yield api2();
        console.log(age);
    }catch (e) {
        console.log("err:" + e);      // err:api2炸了 
    }
    return {
        name,
        age
    };
}
function api1() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("sena");
        }, 1000);
    })
}
function api2() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject("api2炸了");
        }, 1000);
    })
}
run(requestData).then((res) => {
    console.log(res);        // { name: 'sena', age: undefined }
}, (err) => {
    console.log("错误原因 : " + err);
});




好了,一个简单的仿co自动执行函数就写完了,如果有遗漏或错误,欢迎指出

本文地址:http://sakura-snow.com/archives/160

点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像

Title - Artist
0:00