Promise/Generator/Async相关的异步操作

Published on
发布于·预估阅读25分钟
Authors
  • Name
    willson-wang
    Twitter

Promise

Promise异步编程的一种解决方案,被纳入ES6的标准,两个特点,Promise对象的状态不受外界的影响,即只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态;一旦状态改变,就不会再变,任何时候都可以得到这个结果;


var timeout = function (time = 2000) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('222');
        }, time)
    })
}

timeout().then((res) => {
    console.log(res);
})

// p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。
// 由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的
// then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(new Error('fail'))
    }, 3000)
});

const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(p1);
    }, 1000)
})

p2.then((res) => {
    console.log('res', res);
}).catch((error) => {
    console.log('error', error);
});

// then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写
// 法,即then方法后面再调用另一个then方法

//采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是
// 一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才
// 会被调用。

const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('p3');
    }, 2000)
})

const p4 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('p4');
    }, 4100)
})

p3.then((res) => {
    console.log('res p3', res);
    // 注意这里可以return 任何值,可以是之前固定了状态的promise3,也可以是一个新的promise,也可以是
    // 一个常量,都会在后一个then中的回调函数中拿到,但是需要注意的是,如果返回的是一个新的
    // promise,则需要等待promise内的resolve or reject执行玩之后才能够执行then内的回调
    return p4; 
}).then((res) => {
    console.log('res p3 then2', res); // undefined,因为前一个then没有return值
})


// 如果 Promise 状态已经变成resolved,再抛出错误是无效的。同理如果状态变成reject,在resolve就无效
// 了,另外需要注意的是,promise内抛出错误与reject抛出错误是等效的

// Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下个
// catch语句捕获;跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理的回调函数,
// Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。
const p5 = new Promise((resolve, reject) => {
    resolve('p5');
    throw new Error('p5 faile');
});

p5.then((res) => {
    console.log('res p5', res);
}).catch((error) => {
    console.log('p5 error', error);
});

//Promise.prototype.finally() es8引入的语法,要考虑兼容性问题

const p6 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('p6');
    }, 1000)
})


const p7 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('p7')
    }, 2000)
})

const p8 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('p8')
    }, 4000)
})

// Promise.all方法传入的是一个数组promise,如果本身不是promise则会使用promise.resolve()被转化为
// promise对象,promise.all方法的返回值是包含每个promise结果的返回值数组,只有每一个promise都
// resolve才能够成功,如果有一个reject or error则会执行promise.all的catch方法;

// 如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()
// 的catch方法

// promise.all的方法常用于同时请求几个异步接口,但是每个异步接口之间又不互相依赖

const p9 = Promise.all([p6, p7, p8])

p9.then((res) => {
    console.log('res p9 all', res);
}).catch((error) => {
    console.log('error p9 all', error);
})


// pormise.race方法的使用与promise.all方=方法是一样的,区别市race方法是只要某个promise的状态先该
// 变了,那么race方法返回的promise的状态也会进行改变并与率先改变的primise状态保持一致

// promise.race方法常用于设置某个请求是否超时

const p10 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('p10');
    }, 1000)
})


const p11 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('p11')
    }, 500)
})

const p12 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('p12')
    }, 4000)
})

const p13 = Promise.race([p10, p11, p12])

p13.then((res) => {
    console.log('res p12 race', res);
}).catch((error) => {
    console.log('error p12 race', error);
})

const p14 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('p14')
    }, 2000)
})

const p15 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('请求超时')
    }, 1500)
})

const p16 = Promise.race([p14, p15])

p16.then((res) => {
    console.log(res);
}).catch((err) => {
    console.log(err);
})

// promise.resolve()方法,将现有对象or原始值转化为promise对象or直接生成一个状态值为resolved的
// promise对象

// Promise.resolve('aaa') === new Promise((resolve) => {resolve('aaa')})

// 需要注意的是根据传入的参数来返回不同的值
// 1.传入的参数是promise对象,则promise.resolve方法不做任何修改原样返回

const p17 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('p17')
    }, 2000)
})

const p18 = Promise.resolve(p17);

p18.then((res) => {
    console.log(res);
})

// 2. 参数是一个thenable对象,Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行
// thenable对象的then方法

let thenable = {
    then: function(resolve, reject) {
        resolve(42);
    }
};

let p19 = Promise.resolve(thenable);
    p19.then(function(value) {
        console.log(value);
});

// 3. 参数不是具有then方法的对象,或根本就不是对象,则Promise.resolve方法返回一个新的 Promise 对
//象,状态为resolved。由于对象or字符串不属于异步操作(判断方法是字符串对象不具有 then 方法),返
// 回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve方法的参
// 数,会同时传给回调函数。

const p20 = Promise.resolve({
    name: 'jack',
    age: 20
})

p20.then((res) => {
    console.log(res);
})

// 4. 不带有任何参数,Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 
// 对象,立即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件
// 循环”的开始时

const p21 = Promise.resolve();

p21.then((res) => {
    console.log(res, 'p21');
})

// promise.reject()方法返回一个新的promise对象,且该实例的状态为rejected,注意,Promise.reject()方法
// 的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致

const info = {
    name: 'jack',
    age: 20
}
const p22 = Promise.reject(info);

p22.then((res) => {
    console.log(res);
}).catch((e) => {
    console.log(e === info, e); // true
})

// promise.try()

// 这种写法有一个缺点,就是如果f是同步函数,那么它会在本轮事件循环的末尾执行。
const f1 = () => console.log('now');
const p23 = Promise.resolve().then(f1);
console.log('next');

// 那么有没有一种方法,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API 呢?回答是
// 可以的,并且还有两种写法。第一种写法是用async函数来写,async () => f()会吃掉f()抛出的错误。所以,
// 如果想捕获错误,要使用promise.catch方法。另一种写法是使用new promise
const f2 = () => console.log('now2');
(async () => f2())();
console.log('next2');

const f3 = () => console.log('now3');
(
    () => new Promise((resolve) => {
        resolve(f3())
    })
)()
console.log('next3');

// 使用promise.try()来处理函数f不管函数是同步函数还是异步操作,想用 Promise 来处理它。因为这样就可
// 以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误

// const f4 = () => console.log('now4');
// Promise.try(f4).then((res) => {
//     console.log('try', res);
// }).catch((err) => {
//     console.log('try err', err);
// })
// console.log('next4');

Gennerator

Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二 // 是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”;主要用来解决 // 以同步的编码方式来实现异步的过程


function *hello () {
    yield 'i';
    yield 'love'
    yield 'you'
    return 'ending' 
}

// Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用
// Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象;下
// 一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部
// 指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。
// 换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

// next方法返回一个对象,它的value属性就是当前yield表达式的值hello,done属性的值false or true,表
// 示遍历还没有结束和遍历结束

// 总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调
// 用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状
// 态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。

const h = hello(); // 返回的是一个指向内部状态的指针对象
console.log(h);
console.log(h.next()); // {value: "i", done: false}
console.log(h.next()); // {value: "love", done: false}
console.log(h.next()); // {value: "you", done: false}
console.log(h.next()); // {value: "ending", done: true}
console.log(h.next()); // {value: undefined, done: true}

const arr = [1, [[2, 3], 4], [5, 6]];

function *flat(a) {
    const length = a.length;
    for (let i = 0; i < length; i++) {
        const item = a[i];
        if (typeof item !== 'number') {
            yield *flat(item);
        }else {
            yield item;
        }
    }
}

// for...of循环可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法。

for (let f of flat(arr)) {
    console.log(f);
}

// generator函数处理异步请求

function request(url) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(data = {
                name: 'jack',
                age: 18
            })
        }, 3000)
    })
    
}

function *gen() {
    var result = yield request('http://xxx.com');
    console.log('yield之后');
}

const g = gen();
const result = g.next();
console.log(result);

result.value.then((res) => {
    console.log('res', res);
}).then(() => {
    g.next(); // 需要在第一次调用next方法返回promise之后,在promise成功之后在继续调用next方法,可
// 以继续执行下一个yiled之前的代码
}).catch((err) => {
    console.log('err', err);
})

Async

async 函数是什么?一句话,它就是 Generator 函数的语法糖;ES2017标准引入的;使用的时候需要注意,async函数返回的是一个promise;await后面不管跟什么表达式都会被转化为promise对象


function request(url) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(url);
        }, 2000)
    })
}

const gen = function *() {
    const f1 = yield request('http://xxxx.com/api/getCode');
    const f2 = yield request('http://xxxx.com/api/getInfo');
    console.log(f1);
    console.log(f2);
}

const g = gen();
const r1 = g.next()
r1.value.then((res) => {
    console.log('res', res);
    var r2 = g.next(res);
    r2.value.then((res2) => {
        console.log('res2', res2);
        g.next(res2);
    }) 
})

// 改成async的书写方式
async function aGen () {
    const f1 = await request('http://xxxx.com/api/getCode');
    const f2 = await request('http://xxxx.com/api/getInfo');
    console.log('aGen', f1);
    console.log('aGen', f2);
    return {name: 'jack'}
}

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

// 比较async与gennerator
// 书写上async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已
// 1. Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async
// 函数的执行,与普通函数一模一样,只要一行aGen(),完全不像 Generator 函数,需要调用next方法,或
// 者用co模块,才能真正执行,得到最后结果。

// 2. 更好的语义,async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表
// 示紧跟在后面的表达式需要等待结果。

// 3. 更广的适用性,co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命
// 令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)

// 4. 返回值是 Promise,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令
// 就是内部then命令的语法糖

// 基本用法,获取有依赖关系的异步操作,因为async函数返回一个 Promise 对象,可以使用then方法添加
// 回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面
// 的语句

function getCode() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve('84898');
        }, 1000)
    })
}

function getInfo(code) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({
                code,
                name: 'lose'
            })
        }, 3000)
    })
}

async function getUserInfo () {
    const code = await getCode();
    const result = await getInfo(code);
    console.log('result', result);
    return result;
}

getUserInfo().then((res) => {
    console.log('getUserInfo res', res);
}).catch((err) => {
    console.log('getUserInfo err', err);
})

// async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状
// 态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行
// then方法指定的回调函数。

async function f1() {
    return await 123;  // await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。
}

f1().then((res) => {
    console.log('res', res);
})

// 只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。
async function f2() {
    await Promise.reject('error f2');
    console.log('是否执行'); // 不执行了
    await Promise.resolve('succ f2');
}

f2().then((res) => {
    console.log('res f2', res);
}).catch((err) => {
    console.log('res error', err);
})

// 注意await后面的异步操作,是否有依赖关系,如果有则依次执行,没有则并发执行;
// 1. 有依赖关系,需要依次执行,比较耗时
function getVarilCode () {
    return new Promise((resolve) => {
        console.log('先获取手机验证码');
        setTimeout(() => {
            resolve('4678');
        }, 500)
    })
}

function getList () {
    return new Promise((resolve) => {
        console.log('获取用户信息列表');
        setTimeout(() => {
            resolve([
                {
                    book: 'js',
                    id: 1
                },
                {
                    book: 'php',
                    id: 2
                }
            ]);
        }, 1000)
    })
}


async function login(code) {
    const list = await getList(); // 注意这个await只能放在promise外面,不能放里面,因为awite只能放
// async关键字的函数内
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({
                name: 'kils',
                age: 20,
                sex: '男',
                list,
            });
        }, 2000)
    })
}

async function getAutoUserInfo () {
    const newDate = +new Date();
    const code = await getVarilCode(); // await表达式返回的是对应prmise对象,resolve or reject的值,因
// 为await表达式会把后面不是promise的对象转化为promise对象, 所以async函数返回的是一个promise对
// 象,而await表达式返回的是一个具体的值
    const result = await login(code);
    const test = await '123';
    const oldDate = +new Date();
    console.log('result', result, oldDate - newDate, test);
    return result;
}

getAutoUserInfo().then((res) => {
    console.log('getUserInfo res', res);
}).catch((err) => {
    console.log('getUserInfo err', err);
})

// 无依赖关系,可以并行执行,然后等待每个并行执行异步操作的结果

function f3() {
    return new Promise((resolve) => {
        console.log('并行执行 f3');
        setTimeout(() => {
            resolve('f3');
        }, 2000)
    })
}

function f4() {
    return new Promise((resolve) => {
        console.log('并行执行 f4');
        setTimeout(() => {
            resolve('f4');
        }, 2000)
    })
}

async function all() {
    const newDate = +new Date();
    const r1 = f3();
    const r2 = f4();
    const result1 = await r1;
    const result2 = await r2;
    console.log('all', +new Date() - newDate, result1, result2);
}

all();

// async函数实现的原因
function spawn(genF) {
    return new Promise((resolve, reject) => {
        // 获取generator函数
        const gen = genF();
        function step(nextF) {
            let next;
            try{
                next = nextF();
            }catch(e){
                return reject(e);
            }
            // 如果结束,则返回最后一次next.value
            if(next.done) {
                return resolve(next.value);
            }
            // 包装一下next.value,便于promise方式调用
            Promise.resolve(next.value).then((val) => {
                // 如果done不是true,则继续调用next方法
                step(() => {
                    return gen.next(val);
                });
            }, (e) => {
                // 如果报错则,reject错误
                step(() => {
                    return gen.throw(e);
                });
            })
        }
        // 执行第一次next方法
        step(() => {
            return gen.next(undefined);
        });
    })
}

function f5() {
    return new Promise((resolve) => {
        console.log('并行执行 f5');
        setTimeout(() => {
            resolve('f5');
        }, 2000)
    })
}

function f6() {
    return new Promise((resolve) => {
        console.log('并行执行 f6');
        setTimeout(() => {
            resolve('f6');
        }, 2000)
    })
}

function asyncCopy(args) {
    // 返回spawn函数调用的结果
    return spawn(function *() {
        const newDate = +new Date();
        const r1 = f5();
        const r2 = f6();
        const result1 = yield r1;
        const result2 = yield r2;
        console.log('asyncCopy all', +new Date() - newDate, result1, result2);
    })
}

asyncCopy();

常用的异步处理方式

按顺序调用异步操作,分别用Promise/Generator/Async来实现

const animations = [toTop, toRight, toBottom];

function toTop(ele) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve('toTop');
        }, 1000)
    })
}

function toRight(ele) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve('toRight');
        }, 2000)
    })
}

function toBottom(ele) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve('toBottom');
        }, 2000)
    })
}

function animationsPromise (ele, animations) {
    let ret = null;

    let p = Promise.resolve();

    for(let anim of animations) {
        // 利用then函数必须要等道promise的状态发生改变之后才会继续后面的then操作
        p = p.then((val) => {
            console.log('val', val);
            ret = val;
            return anim(ele);
        })
    }
    console.log('p', p);
    return p.catch((e) => {}).then((res) => {
        console.log('last res', res);
        return res ? res : ret;
    })
}

// animationsPromise(null, animations).then((res) => {
//   console.log('res', res);
// }).catch((err) => {
//     console.log('err', err);
// });

function animationsGenerator(elem, animations) {
    function spawn(genF) {
        return new Promise((resolve, reject) => {
            // 获取generator函数
            const gen = genF();
            function step(nextF) {
                let next;
                try{
                    next = nextF();
                }catch(e){
                    return reject(e);
                }
                // 如果结束,则返回最后一次next.value
                if(next.done) {
                    return resolve(next.value);
                }
                // 包装一下next.value,便于promise方式调用
                Promise.resolve(next.value).then((val) => {
                    // 如果done不是true,则继续调用next方法
                    step(() => {
                        return gen.next(val);
                    });
                }, (e) => {
                    // 如果报错则,reject错误
                    step(() => {
                        return gen.throw(e);
                    });
                })
            }
            // 执行第一次next方法
            step(() => {
                return gen.next(undefined);
            });
        })
    }

    return spawn(function *() {
        let ret = null;
        
        try{
            for (let prop of animations) {
                ret = yield prop(elem)
            } 
        }catch(e) {

        }
        return ret;
    })
}

animationsGenerator(null, animations).then((res) => {
    console.log('animationsGenerator res', res);
}).catch((err) => {
    console.log('animationsGenerator err', err);
});

// 依次执行,并返回最后一次的结果
async function animationsAsync(elem, animations) {
    let ret = null;
    try {
        for(let anim of animations) {
            ret = await anim(elem);
        }
    } catch(e) {
        /* 忽略错误,继续执行 */
        console.log('e', e);
    }
    return ret;
}

// animationsAsync(null, animations).then((res) => {
//     console.log('animationsAsync res', res);
// }).catch((err) => {
//     console.log('animationsAsync err', err);
// });

顺序调用与并发调用的区别

function getUrl(url, time = 1000) {
return new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve({
        code: 0,
        data: {
        time: +new Date(),
        url,
        list: [1, 2, 5]
        }
    });
    }, time)
})
}

function getCode() {
return getUrl('/api/getCode', 1000);
}

function getInfo() {
return getUrl('/api/getInfo', 2000);
}

// 链式调用处理顺序promise,该方法多个顺序promise调用是,书写不便
function fn() {
function recode(results, value){
    results.push(value);
    return results;
}

// bind方法,fun.bind(thisArg[, arg1[, arg2[, ...]]]),当绑定函数被调用时,这些参数将置于实参之前传递给
// 被绑定的方法。
const pushValue = recode.bind(null, []); // 

// return getCode().then(pushValue).then(getInfo).then(pushValue);
// const arr = [];
// return getCode().then((res) => {
//   arr.push(res);
// }).then(() => {
//   return getInfo();
// }).then((res) => {
//   arr.push(res);
//   return arr;
// })

const arr = [];
return getCode().then((res) => {
    arr.push(res);
}).then(getInfo).then((res) => {
    arr.push(res);
    return arr;
})
} 

// fn().then((res) => {
//   console.log('fn res', res);
// }).catch((err) => {
//   console.log('fn err', err); 
// })

// for循环,调用顺序promise

function fn1() {
function recode(results, value){
    results.push(value);
    return results;
}

const pushValue = recode.bind(null, []);

const task = [getCode, getInfo];

let p = Promise.resolve();

// 通过不断对promise进行处理,不断的覆盖 p 变量的值,以达到对p对象的累积处理效果。一定要先声明
// 一个p变量
for(let prop of task) {
    p = p.then(prop).then(pushValue);
}

return p;
}

// fn1().then((res) => {
//   console.log('fn1 res', res);
// }).catch((err) => {
//   console.log('fn1 err', err); 
// })

// 利用reduce方法来进行处理

function fn2() {
    function recode(results, value){
        results.push(value);
        return results;
    }

    const pushValue = recode.bind(null, []);

    const task = [getCode, getInfo];
    return task.reduce(function (promise, task) {
        return promise.then(task).then(pushValue);
    }, Promise.resolve());
}

// fn2().then((res) => {
// console.log('fn1 res', res);
// }).catch((err) => {
// console.log('fn1 err', err); 
// })

// 使用async来处理,顺序处理
async function fn3() {
    const tasks = [getCode, getInfo];
    let res;
    for (let task of tasks) {
        res = await task();
        console.log(res);
    }
    return res;
}

// fn3().then((res) => {
//     console.log('fn3 res', res);
// }).catch((err) => {
//     console.log('fn3 err', err);
// })

// 使用async来处理,并发处理,并发与顺序的区别是,并发是await promise的结果,顺序是await整个promise过程
async function fn4() {
    const newDate = +new Date();
    const tasksPromise = [getCode(), getInfo()];
    let res;
    for (let task of tasksPromise) {
        res = await task;
        console.log(res);
    }
    console.log(+new Date - newDate);
    return res;
}

fn4().then((res) => {
    console.log('fn4 res', res);
}).catch((err) => {
    console.log('fn4 err', err);
})