• callback vs async.js vs promise vs async / await


    需求:

    A、依次读取 A|B|C 三个文件,如果有失败,则立即终止。
    B、同时读取 A|B|C 三个文件,如果有失败,则立即终止。

    一、callback


    需求A

      
    let read = function (code) {
        if (code) {
            return true;
        } else {
            return false;
        }
    }
    
    let readFileA = function (callback) {
        if (read(1)) {
            return callback(null, "111");
        } else {
            return callback("a fail");
        }
    }
    let readFileB = function (callback) {
        if (read(1)) {
            return callback(null, "222");
        } else {
            return callback("b fail");
        }
    }
    let readFileC = function (callback) {
        if (read(1)) {
            return callback(null, "333");
        } else {
            return callback("c fail");
        }
    }
    
    readFileA(function (err, data) {
        if (err) {
            console.log("open file " + err);
            return;
        }
        console.log("读取 a.txt 成功!内容:" + data);
        readFileB(function (err, data) {
            if (err) {
                console.log("open file " + err);
                return;
            }
            console.log("读取 b.txt 成功!内容:" + data);
            readFileC(function (err, data) {
                if (err) {
                    console.log("open file " + err);
                    return;
                }
                console.log("读取 c.txt 成功!内容:" + data);
            });
        });
    });
    
    

    return:

    读取 a.txt 成功!内容:111
    读取 b.txt 成功!内容:222
    读取 c.txt 成功!内容:333
    

    需求B:太恶心了,不写了,总之很繁琐.

    二、async.js


    async.js 库的详细介绍可以见:[待写]

    需求A

    async.series

    var async = require("async");
    
    let read = function (code) {
        if (code) {
            return true;
        } else {
            return false;
        }
    }
    
    let readFileA = function (callback) {
        if (read(1)) {
            return callback(null, "111");
        } else {
            return callback("a fail");
        }
    }
    
    let readFileB = function (callback) {
        if (read(0)) {
            return callback(null, "222");
        } else {
            return callback("b fail");
        }
    }
    let readFileC = function (callback) {
        if (read(1)) {
            return callback(null, "333");
        } else {
            return callback("c fail");
        }
    }
    
    async.series([readFileA, readFileB, readFileC],
        function (err, datas) {
            if (err) {
                console.log("open file " + err);
            }
            console.log(datas);
            return;
        });
    

    当第二个 readFileB() 读取失败的话:

    return:

    open file b fail
    [ '111', undefined ]
    

    需求B

    async.parallel

    var async = require("async");
    
    let read = function (code) {
        if (code) {
            return true;
        } else {
            return false;
        }
    }
    
    let readFileA = function (callback) {
        if (read(1)) {
            return callback(null, "111");
        } else {
            return callback("a fail");
        }
    }
    
    let readFileB = function (callback) {
        setTimeout(() => {
            if (read(0)) {
                return callback(null, "222");
            } else {
                return callback("b fail");
            }
        }, 1000);
    }
    
    let readFileC = function (callback) {
        if (read(1)) {
            return callback(null, "333");
        } else {
            return callback("c fail");
        }
    }
    
    async.parallel([readFileA, readFileB, readFileC],
        function (err, datas) {
            if (err) {
                console.log("open file " + err);
            }
            console.log(datas);
            return;
        });
    
    

    当第二个 readFileB() 读取失败 (注意我给它加了 setTimeout,为了体现跟上面串行结果的不一样) 的话:

    return:

    open file b fail
    [ '111', undefined, '333' ]
    

    总结:async.js 跟 callback 比的好处:

    1、代码量少了,解决了回调地狱金字塔的缺陷

    2、async 的第二个参数回调函数里,可以统一处理错误(建议用不同的 Error 类作区分)

    3、成功返回的结果 datas 可以汇总到一个数组中方便处理

    三、promise


    [拓展]

    promise 知识

    new Promise()


    //  promise 在 new 的时候已经开始运行
     new Promise(() => console.log("I have already started!"));
    

    return:

    I have already started!
    

    promise.then(successCallback, failureCallback);


    new Promise((resolve, reject) => resolve()).then(function (data) {
        console.log("success");
    }, function (data) {
        console.log("fail");
    })
    

    return:

    success
    

    promise.catch(failureCallback)


    // promise.catch(failureCallback) 是 promise.then(null, failureCallback) 的缩略形式
    new Promise((resolve, reject) => reject()).catch( function (data) {
        console.log("fail");
    })
    

    return:

    fail
    

    链式调用


    // 链式调用的原理:then 函数会返回一个新的 promise
    new Promise((resolve, reject) => reject()).then(function (data) {
        console.log("success_1");
    }, function (err) {
        console.log("fail_1");
    }).then(function (data) {
        console.log("success_2");
    }, function (err) {
        console.log("fail_2");
    });
    

    return:

    fail_1
    success_2
    

    提问

    问1:then 函数会返回一个新的 promise,但是 then 的 successCallback 和 failureCallback 这两个回调函数里都没法调用 resolve() 和 reject(),那这个新的 promise 如何指定最终状态呢?

    then 的 successCallback 和 failureCallback 里 等同于
    不返回 resolve(undefined)
    return 1 resolve(1)
    return Promise.resolve() resolve()
    return Promise.reject() reject()
    throw Error() reject()
    return new Promise() 以此类推

    而普通的 promise 对象,如果不显示调用 resolve/reject ,则没有任何反应,例如:

    new Promise((resolve, reject) => {return 1;}).then(function (data) {
        console.log("success");
    }, function (err) {
        console.log("fail");
    });
    

    return:

    没有任何输出

    问2:then 函数如果 successCallbackfailureCallback 都为 null,会发生什么?

    什么都不会发生,.then(null, null) 只要一方为 null,等于交给下一个 then 去接管这个回调

    new Promise((resolve, reject) => reject())
        .then(null, null)
        .then(null, null)
        .then(null, null)
        .then(null, null)
        .then(function (data) {
            console.log("success_2");
        }, function (err) {
            console.log("fail_2");
        });
    

    所以按照上面 2 个提问揭示的规律,我们可以写成下面优雅的代码

    // 链式调用的原理:then 函数会返回一个新的 promise
    new Promise((resolve, reject) => resolve()).then((data) => {
        console.log("success_1");
    }).then((data) => {
        console.log("success_2");
        throw Error("error");
    }).then((data) => {
        console.log("success_3");
    }).catch((err) => {
        console.log(err);
    }); 
    

    return:

    success_1
    success_2
    Error: error ……
    

    注:.catch() 后还可以继续接 .then().catch()

    这就达到了如下别人家同步代码的清晰的表达:

    try {
        let result = syncDoSomething();
        let newResult = syncDoSomethingElse(result);
        let finalResult = syncDoThirdThing(newResult);
        console.log(`Got the final result: ${finalResult}`);
      } catch(error) {
        console.log(error);
    }
    

    所以,需求A:

    
    let read = function (code) {
        if (code) {
            return true;
        } else {
            return false;
        }
    }
    
    let readFileA = function () {
        return new Promise(function (resolve, reject) {
            if (read(1)) {
                resolve("111");
            } else {
                reject("a fail");
            }
        });
    }
    let readFileB = function () {
        return new Promise(function (resolve, reject) {
            if (read(1)) {
                resolve("222");
            } else {
                reject("b fail");
            }
        });
    }
    let readFileC = function () {
        return new Promise(function (resolve, reject) {
            if (read(1)) {
                resolve("333");
            } else {
                reject("c fail");
            }
        });
    }
    
    //[串行] 场景:依次预加载多个资源,如果中途有失败,则进入 .catch()
    readFileA().then(function (data) {
        console.log("读取 a.txt 成功!内容:" + data);
        return readFileB();
    }).then(function (data) {
        console.log("读取 b.txt 成功!内容:" + data);
        return readFileC();
    }).then(function (data) {
        console.log("读取 c.txt 成功!内容:" + data);
        return "读取结束";
    }).then(function (data) {
        console.log(data);
        return;
    }).catch(function (err) {
        console.log("open file " + err);
    })
    
    

    promise vs 事件监听

    a. 事件监听更多的是针对同一对象上发生多次的事情(如 keyup、touchstart 等)

    promise 更多的表现这个对象最终走向什么状态,且不可改变。

    但有个神奇的特型是一致的,事件监听promise 都可以对同一事件的反应绑定多次的回调函数,如下面例子所示:

    let promise = new Promise((resolve, reject) => {
        console.log("I have already started!");
        resolve();
    })
    
    setTimeout(() => { 
        promise.then(function (data) {
            console.log("success_1");
            throw new Error();
        }, function (err) {
            console.log("fail_1");
        }); 
    }, 2000);
    
    setTimeout(() => { 
        promise.then(function (data) {
            console.log("success_2");
        }, function (err) {
            console.log("fail_2");
        }); 
    }, 4000);
    

    return:

    I have already started!
    //又等待了2秒
    success_1
    (node:13150) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error
    (node:13150) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
    //又等待了2秒
    success_2
    

    b. 跟 事件监听不一样,如果 promise 已成功或失败了,过段时间再添加了回调函数,则还是可以成功调用回调。这个上面的例子也可以体现。

    c. 事件监听更多的关注某些功能的准确时间,promise 更多地是关注对结果作出的反应。


    promise 扩展 API

    Promise.resolve()Promise.reject()


    手动创建一个已经 resolve 或者 reject 的 promise 的快捷方法。

    promise.all:可以实现需求B:


    //promise.all [并行] 场景:预加载多个资源,都完成后才能进入页面
    Promise.all([readFileA(), readFileB(), readFileC()]).then(function (datas) {
        console.log(datas); //所有promise都resolve,返回array
        return;
    }).catch(function (err) {
        console.log("open file " + err); //只要有一个promise是reject,返回这个reject的value
    })
    

    promise.race


    //promise.race [并行] 场景:taskA:fetch图片,taskB:settimeout抛错,让两个task赛跑实现请求超时报错功能
    Promise.race([taskA(), taskB()]).then(function (data) { //进到resolve还是reject回调只取决于第一个确定状态的Promise
        console.log(data);
        return;
    }).catch(function (err) {
        console.log("读取图片超时");
    })
    

    总结:promise 跟 callback 比的好处:

    1、代码量少了,解决了回调地狱金字塔的缺陷

    2、.catch 可以统一处理错误(建议用不同的 Error 类作区分)

    四、async / await

    需求A:

    let read = function (code) {
        if (code) {
            return true;
        } else {
            return false;
        }
    }
    
    let readFileA = function () {
        return new Promise(function (resolve, reject) {
            if (read(1)) {
                resolve("111");
            } else {
                reject("a fail");
            }
        });
    }
    let readFileB = function () {
        return new Promise(function (resolve, reject) {
            if (read(1)) {
                resolve("222");
            } else {
                reject("b fail");
            }
        });
    }
    let readFileC = function () {
        return new Promise(function (resolve, reject) {
            if (read(1)) {
                resolve("333");
            } else {
                reject("c fail");
            }
        });
    }
    
    async function test() {
        try {
            let re_a = await readFileA();
            let re_b = await readFileB();
            let re_c = await readFileC();
            console.log({re_a, re_b, re_c}); //如果都成功,return: { re_a: '111', re_b: '222', re_c: '333' }
        } catch (err) {
            console.log(err); // 如果b失败,return: b fail
        }
    }
    
    test(); 
    

    总结:async / await 跟 callback 比的好处:

    1、代码量最少,解决了回调地狱金字塔的缺陷(Promise 通过 then 链来解决 callback 多层回调金字塔的问题,现在又用 async/await 来进一步优化它)(基于 promise 的 async / await 也试图淘汰 promise)

    2、.catch 可以统一处理错误(建议用不同的 Error 类作区分)


    [拓展]

    1、async 函数就是 Generator 函数的语法糖,本质上并不是同步代码

    2、async 用于申明一个 function 是异步的,而 await (async wait) 用于等待一个异步方法执行完成。

    3、await 只能出现在 async 函数中,所以在代码的顶层,我们无法使用 await,所以添加它 .then/catch 来处理最终结果或掉落错误是正常的做法。

    try {
            let re_a = await readFileA();
            let re_b = await readFileB();
            let re_c = await readFileC();
            console.log({re_a, re_b, re_c});
        } catch (err) {
            console.log(err);
        }
    

    return:

    报错

    或者顶层使用立即执行函数表达式(IIFE)

    (async () => {
    
        try {
            let re_a = await readFileA();
            let re_b = await readFileB();
            let re_c = await readFileC();
            console.log({re_a, re_b, re_c});
        } catch (err) {
            console.log(err);
        } 
    
    })()
    

    return:

    { re_a: '111', re_b: '222', re_c: '333' }
    

    上面的例子还可以这样写:

    async function test() {
        try {
            let re_a = await readFileA();
            let re_b = await readFileB();
            let re_c = await readFileC();
            console.log({re_a, re_b, re_c}); //如果都成功,return: { re_a: '111', re_b: '222', re_c: '333' }
        } catch (err) {
            console.log(err); // 如果b失败,return: b fail
        }
    }
    
    test().then(function(data){
        console.log("success");
    },function(err){
        console.log("fail"); 
    }); 
    

    return:

    { re_a: '111', re_b: '222', re_c: '333' }
    success
    

    4、见上例,实际上 async 申明的 function 返回的就是一个 Promise 对象,这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。

    区别是,async 申明的 function 里可以通过 return值 / 抛异常 来实现普通 Promise 的 resolve() / reject()

    下面是对等关系:

    // async 函数
    async function foo () {
      return 'a'
    }
    // Promise
    function foo () {
      return Promise.resolve('a')
    }
    
    // async 函数
    async function foo () {
      throw new Error('error')
    }
    // Promise
    function foo () {
      return Promise.reject(new Error('error'))
    }
    

    promise.all 实现需求B

    async/await 同样适用于 Promise.all,因为 Promise.all 本身返回的就是 promise 对象。

    let read = function (code) {
        if (code) {
            return true;
        } else {
            return false;
        }
    }
    
    let readFileA = function () {
        return new Promise(function (resolve, reject) {
            if (read(1)) {
                resolve("111");
            } else {
                reject("a fail");
            }
        });
    }
    let readFileB = function () {
        return new Promise(function (resolve, reject) {
            if (read(1)) {
                resolve("222");
            } else {
                reject("b fail");
            }
        });
    }
    let readFileC = function () {
        return new Promise(function (resolve, reject) {
            if (read(1)) {
                resolve("333");
            } else {
                reject("c fail");
            }
        });
    }
    
    async function test() {
        try {
            let re_a = await readFileA();
            let re_b = await readFileB();
            let re_c = await readFileC();
            console.log({re_a, re_b, re_c});
        } catch (err) {
            console.log(err);
        }
    }
    
    async function test() {
        try { 
            let results = await Promise.all([
                readFileA(),
                readFileB(),
                readFileC(),  
              ]);
            console.log(results);
        } catch (err) {
            console.log(err);
        }
    }
    
    test();
    

    参考资料

    [使用 promises]
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises

    [理解 JavaScript 的 async/await]
    https://segmentfault.com/a/1190000007535316

    [javascript.info-Async/await]
    https://javascript.info/async-await#async-functions

  • 相关阅读:
    Go:获取命令行参数
    Go:文件操作
    Go:类型断言
    GO:interface
    Go:面向"对象"
    Go:工厂模式
    layui中流加载layui.flow
    js显示当前时间
    layui中的分页laypage
    layui中的多图上传
  • 原文地址:https://www.cnblogs.com/xjnotxj/p/9477987.html
Copyright © 2020-2023  润新知