promise/generator-yield/sync-await的演变

115 阅读2分钟

比如说有这么个场景:首先请求登陆接口,返回{username: '小明'}。然后请求获取额度接口,返回{amt:3434}。将amt展示在页面上

1、最开始的回调

这是最开始的写法,一层一层的回调

$.post('login.do', res => {
    let username = res.username;
    $.get('getAmt.do?username' + username, res => {
        console.log('页面渲染可用金额', res.amt);
    });
});

2、promise写法

Promise出来后的写法

$.post('login.do').then(res => {
    let username = res.username;
    $.get('getAmt.do?username' + username).then(res => {
        console.log('页面渲染可用金额', res.amt);
    });
});

2、generator+yield的写法

generator+yield是es6提出的。

(1)什么是generator

generator其实也是就是一个函数,在function和函数名之间有*,表示这是一个generator函数,generator函数直接调用返回的是一个generator对象,不会立即执行函数里面的内容,当调用next()后才会执行,每一个next()都执行到yield 比如下面代码:

function* g() {
    yield '这是a';
    yield '这是b';
    yield '这是c';
    return '这是return';
}

var gen = g();
gen.next(); // 返回Object {value: "这是a", done: false}
gen.next(); // 返回Object {value: "这是b", done: false}
gen.next(); // 返回Object {value: "这是c", done: false}
gen.next(); // 返回Object {value: "这是return", done: true}
gen.next(); // 返回Object {value: undefined, done: true}

其中next的返回值,value值是yield后面的值,done表示是否还有下一层可以执行。

如果最后没有return '这是return',即代码如下

function* g() {
    yield '这是a';
    yield '这是b';
    yield '这是c';
}

var gen = g();
gen.next(); // 返回Object {value: "这是a", done: false}
gen.next(); // 返回Object {value: "这是b", done: false}
gen.next(); // 返回Object {value: "这是c", done: false}
gen.next(); // 返回Object {value: undefined, done: true}

可以看到最后的next也是返回done=true

(2)yield的返回值

yield的右边会作为返回值返回到next()的value,可以是字符串、布尔型、json、promise等任何数据

(3)yield的参数

next()的参数会作为yield的左边值传递过去。

function* g(str1) {
    console.log('str1', str1); // str1: 参数1
    let str2 = yield str1 + 'a';
    console.log('str2', str2); // str2: 参数3
    let str3 = yield str1 + 'b';
    console.log('str3', str3); // str3: 参数4
}

var gen = g('参数1'); // 这里传达了str1
gen.next('参数2'); // 第一个next是无法传参过去的,没有什么意义
gen.next('参数3');
gen.next('参数4');

具体执行过程如下图

除了第一次next外,每一次next的参数,会赋值给yield左边的变量。

(4)改造promise

generator + promise可以实现同步写法实现异步逻辑,代码如下:

function* g(username, password) {
    let res1 = yield $.post('login.do', {username, password});
    let res2 = yield $.post('getAmt.do', {username: res1.username});
    console.log('res2的结果', res2);
    return false;
}

var gen = g('小明', '小明密码');
gen.next().value.then(res => { // value是$.post的promise,请求login.do
    gen.next(res).value.then(res => { // 请求getAmt.do
        gen.next(res); // 请求getAmt.do后的处理
    });
});

从上面看护,在generator函数里面,大体已经像同步的写法了,但是对面gen又陷入了回调的地狱。

3、封装好runner函数库

不难发现,对于generator的调用,实际上就是一个递归,当发现done=false就执行next(),否则就说明递归结束了

// 封装runner
function runner (fn) {
    var o = fn();
    _next();

    function _next(lastResult) {
        let result = o.next(lastResult);
        if (!result.done) {
            // done=false表示还没有结束,进入下一轮递归
            result.value.then(res => {
                _next(res);
            });
        } else {
            // done=true表示最后一轮,结束递归
        }
    }
}

runner(function *() {
    let res1 = yield $.get('/1.json', {username: '小明', password: '小明密码'});
    console.log('res1', res1);
    let res2 = yield $.get(`/2.json?login=${res1.name}`);
    console.log('页面渲染金额', res2.amt);
    return false;
});

封装好runner后,页面上只需要引入,并关注与里面业务代码逻辑就可以,实际上这个也是async+await的雏形

4、async+await

到了es7,推出了async+await,async相当于上面的runner方法,await相当于上面的yield关键词

async function g (username, passowrd) {
    let res1 = await $.get('/1.json', {username: '小明', password: '小明密码'});
    console.log('res1', res1);
    let res2 = await $.get(`/2.json?login=${res1.name}`);
    console.log('页面渲染金额', res2.amt);
}
g('小明', '小明密码');

这种就可以用同步写法实现异步操作