记录js中的异步方法

194 阅读3分钟

Genertor 函数

比较容易迷惑的点:

function *foo(x) {
    let y = 2 * (yield (x + 1))
    let z = yield (y / 3)
    return (x + y + z)
}
let it = foo(5)
console.log(it.next())   // => {value: 6, done: false}
console.log(it.next(12)) // => {value: 8, done: false}
console.log(it.next(13)) // => {value: 42, done: true}
  • let it = foo(5) 返回一个迭代器
  • console.log(it.next()) 函数暂停在yield (x + 1),返回5+1等于6
  • console.log(it.next(12)) 传参12,上一个yield的返回值为12,let y = 2 * 12,所以 value = 8
  • console.log(it.next(13)) 执行到这一步时, z = 13,x+y+z = 5+24+13 = 42;
Genertor thunk版本异步
  • 什么是thunk函数? 比如说:
function isType(type) {
    return (val)=>{
        return Object.prototype.toString.call(val) === `[object ${type}]`
    }
}
let isString = isType('String');
let isNumber = isType('Number');
console.log(isString('abc'));
console.log(isNumber(123));

isType这样的函数我们称之为thunk函数,根据特定需求,返回定制化的函数,去完成特定的功能。

以文件操作为例,看Genertor如何用于异步,

function readTunnk(fileName) {
    return (callback)=>{
        fs.readFile(fileName, callback)
    }
}

readTunnk是一个thunk函数,目的是为读写的每一个文件绑定定制化回调函数,异步的核心一环就是绑定回调函数,这样就是Genertor异步关联起来了。next执行机制使thunk函数返回了定制化函数,并绑定在next的执行结果里边,这样关联起来。

然函数完整的执行下

const gen =  function* (params) {
    let file1 = yield readTunnk('001.txt');
    console.log(file1.toString());
    let file2 = yield readTunnk('002.txt');
    console.log(file2.toString());
}

let g = gen();
/*
    第一步调用next,开始执行,next返回的value是一个定制化的函数,
    需要一个函数作为参数,座位读取文件后的回调函数
    第二步,传递data1作为参数传入,程序继续执行
    第三步,同理data2作为参数传入
*/  
g.next().value((err, data1)=>{
    g.next(data1).value((err,data2)=>{
        g.next(data2);
    })
})

打印结果如下

001.txt的内容 
002.txt的内容

如果多层嵌套上边的就会很复杂,封装下代码

/*
    如果res.done == false 
    就需要再次调用next 
    直到 res.done == true,
    这不就是迭代吗
*/ 
const runGen = function (g) {
    const next = function (err,data) {
        const res = g.next(data);
        if(res.done) return;
        res.value(next);
    }
}
runGen(gen());

这就是thunk函数的Genertor异步版本。Promise当然也可以实现,怎么实现呢?

Genertor Promise版本异步

原理基本上一样的

const promiseGen = function (str) {
    return new Promise((resolve,reject)=>{
        setTimeout(() => {
            resolve(str);
        }, 1000);
    })
}

const gen = function* () {
    let str1 = yield promiseGen('abc');
    console.log(str1);
    let str2 = yield promiseGen('def');
    console.log(str2);
};

promiseGen函数,我们模拟异步一秒后决议str,是不是和thunk函数有点像,执行thunk函数返回一个定制化函数,执行promiseGen返回一个特定的promise,只不过是写法少有不同,

看下执行,

let g = gen();
//  g.next().value等于定制化promise 然后调用then
//  return g.next(str1).value 拿到 'abc'然后传参给下一个 yield
g.next().value
.then(str1=>{
    return g.next(str1).value
})
.then(str2=>{
    return g.next(str2)
})

查看输出

image.png

一秒后打印出了abc,隔一秒打印出了def

promise版本还是存在嵌套过多的问题,再来封装,和thunk函数封装基本一样

let g = gen();
const runGen = function (g) {
    const next = function(data){
        const res = g.next(data);
        if(res.done) return;
        res.value.then((data2)=>{
            next(data2)
        })
    }
    next();
};
runGen(g);
采用co库

我们针对thunk函数promise两种Genertor异步操作的一次性执行做了封装,但实际场景已经有了成熟的工具包,就是co库,其核心代码我们已经写过了,只不过源码会对各种边界情况做处理,食用方法:

const co = require('co');
let g = gen();
co(g).then(res =>{
    console.log(res);
})

好了已经完成了,真不愧是天生一对。

async/await

async/await被称为异步的终极解决方案,来看下用法

const promiseGen = function (str) {
    return new Promise((resolve,reject)=>{
        setTimeout(() => {
            resolve(str);
        }, 1000);
    })
}

async function logFunc(params) {
    const str1 = await promiseGen('abc');
    console.log(str1);
    const str2 = await promiseGen('def');
    console.log(str2);
}

logFunc();

看下输出

image.png

真不愧是终极解决方案,没有那么多封装,而且书写格式符合人们大脑的思考方式同步运行,真的是厉害。

接下来我们深入一点,有没有发现这个和我们实现的自执行的Genertor函数有点像

runGen 和 yield

实际上async和await就是上边我们对Genertor的封装,是一个语法糖;

参考文章: