JavaScript:從Callback到Async的發展過程——例項演示

AlexShan發表於2018-04-06

閱讀時間:12 minutes 文章型別:理論知識 & 案例演示 案例需求:用JavaScript實現,3個小球先後運動,完成接力賽跑 案例原始碼:見文章最後

引言:

前端開發中,非同步處理必不可少; 過去,我們經常用回撥函式來完成非同步處理,因此也經常產生回撥地獄(callback hell); 今天,我們用例項來對比非同步處理的方法; 是時候用async來處理我們的非同步流程了。

從Callback到Async的進化過程--alexshan


Step 1: 回撥函式 Callback

回撥函式: 是將一個函式作為引數,傳遞給另一個函式,然後在外部函式中呼叫該函式來完成某種例程或動作。

用法:在函式內部呼叫函式

2. 用callback實現小球移動的方法;

function move(ele, target, callback) {
    let left = parseInt(getComputedStyle(ele)["left"]);
    let timer = setInterval(function () {
        if (left >= target) {
            clearInterval(timer);
            callback();
        } else {
            left += 2;
            ele.style.left = left + "px";
        }
    }, 4)
}
複製程式碼

2. 執行運動;

move(ball1, 200, function () { 
    move(ball2, 400, function () {
        move(ball3, 600, function () {
            alert("callback");
        });
    })
})
複製程式碼

Step 2: Promise物件Promise

Promise: 是一個返回值的代理,它允許您將處理程式與非同步操作的最終成功值或失敗原因相關聯。 這使非同步方法可以像同步方法那樣返回值:不是立即返回最終值,而是非同步方法返回一個Promise,以便在未來的某個時間點提供該值。

用法:Promise 物件是由關鍵字 new 及其建構函式來建立的。該函式接收一個函式(executor function)作為它的引數。這個函式接受兩個函式——resolvereject ——作為其引數。當非同步任務順利完成且返回結果值時,會呼叫 resolve 函式;而當非同步任務失敗且返回失敗原因(通常是一個錯誤物件)時,會呼叫reject 函式。

1. 用Promise實現小球移動的方法;

// 讓move方法擁有Promise功能

function move(ele, target) { 
    return new Promise(function (resolve, reject) {
        let left = parseInt(getComputedStyle(ele)["left"]);
        let timer = setInterval(function () {
            if (left >= target) { 
                clearInterval(timer); 
                resolve(); 
            } else { 
                left += 2; 
                ele.style.left = left + "px"; 
            }
        }, 4)
    })
}
複製程式碼

2. 執行運動,呼叫Promise.then()方法;

move(ball1, 200).then(function () {
    return move(ball2, 400);
}).then(function () {
    return move(ball3, 600);
}).then(function () {
    alert("promise");
})
複製程式碼

Step 3: Generator物件 Generator

Generator:生成器函式在函式執行時能暫停,還能從暫停處繼續執行,相當於將函式分段執行。

用法:必須用.next()配合 yield關鍵字使用;例如: function *gen(){ yield 10; y=yield 'foo'; yield y; } var gen_obj=gen(); console.log(gen_obj.next()); // 執行 yield 10,返回 10 console.log(gen_obj.next()); // 執行 yield 'foo',返回 'foo' console.log(gen_obj.next(10)); // 將 10 賦給上一條 yield 'foo' 的左值,即執行 y=10,返回 10 console.log(gen_obj.next()); // 執行完畢,value 為 undefined,done 為 true

1. 用Genertor實現小球移動的方法;

// 函式move方法呼叫上面Promise中的move方法;
function move(ele, target) { 
    return new Promise(function (resolve, reject) {
        let left = parseInt(getComputedStyle(ele)["left"]);
        let timer = setInterval(function () {
            if (left >= target) { 
                clearInterval(timer); 
                resolve(); 
            } else { 
                left += 2; 
                ele.style.left = left + "px"; 
            }
        }, 4)
    })
}
複製程式碼

2. 執行運動,需要分佈執行,但此方法需要手動分行執行;

let g = m();
g.next(); //讓第一個小球運動;
g.next(); //讓第二個小球運動;
g.next(); //讓第三個小球運動;
複製程式碼

3. 使用co庫迭代generator執行器;

function co(it) {
    return new Promise(function (resolve, reject) {
        function next(d) {
            let { value, done } = it.next(d);
            if (!done) {
                value.then(function (data) {
                    next(data)
                }, reject)
            } else {
                resolve(value);
            }
        };
        next();
    });
}
// 一行程式碼實現函式執行,但是需要引入co庫;
co(m()).then(function () {
    alert("generator");
})
複製程式碼

Step 4: async/await函式 async/await

async: 非同步函式宣告定義了一個非同步函式,它返回一個AsyncFunction物件。當async函式執行,返回一個Promise物件;

用法:用async宣告函式,函式內配合await使用。

1. 用 async/await實現小球移動的方法;

// 呼叫上面的move()方法;
function move(ele, target) { 
    return new Promise(function (resolve, reject) {
        let left = parseInt(getComputedStyle(ele)["left"]);
        let timer = setInterval(function () {
            if (left >= target) { 
                clearInterval(timer); 
                resolve(); 
            } else { 
                left += 2; 
                ele.style.left = left + "px"; 
            }
        }, 4)
    })
}
複製程式碼

2. 執行運動,函式內await方法;

async function a() {
    await move(ball1, 200);
    await move(ball2, 400);
    await move(ball3, 600);
}
a().then(function () {
    alert("async")
})
複製程式碼

結語:

通過上述4種方法的對比,我們可以看出JavaScript這門語言的發展和進步; ES6+增加了很多實用功能和方法,將有助於前期程式碼的編寫以及後期程式碼的維護,是時候用 async/await來處理我們的非同步操作了。


案例原始碼:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .container .ball {
            width: 100px;
            height: 100px;
            border-radius: 50%;
            position: absolute;
        }

        .container .ball:nth-child(1) {
            background-color: blue;
            left: 0;
            top: 20px;
        }

        .container .ball:nth-child(2) {
            background-color: yellow;
            left: 200px;
            top: 150px;
        }

        .container .ball:nth-child(3) {
            background-color: green;
            left: 400px;
            top: 280px;
        }
    </style>
</head>

<body>
    <div class="container">
        <div class="ball"></div>
        <div class="ball"></div>
        <div class="ball"></div>
    </div>
    <!-- <script src="Promise.js"></script> -->
    <script>
        let ball = document.querySelectorAll(".ball");
        let [ball1, ball2, ball3] = [...ball];

        // 1.回撥函式處理;
        function move(ele, target, callback) {
            let left = parseInt(getComputedStyle(ele)["left"]);
            let timer = setInterval(function () {
                if (left >= target) {
                    clearInterval(timer);
                    callback();
                } else {
                    left += 2;
                    ele.style.left = left + "px";
                }

            }, 4)
        }
        move(ball1, 200, function () {
            move(ball2, 400, function () {
                move(ball3, 600, function () {
                    alert("callback");
                });
            })
        })

        // 2.promise
        // generator、async都是基於promise的發展;
        // function move(ele, target) {
        //     return new Promise(function (resolve, reject) {
        //         let left = parseInt(getComputedStyle(ele)["left"]);
        //         let timer = setInterval(function () {
        //             if (left >= target) {
        //                 clearInterval(timer);
        //                 resolve();
        //             } else {
        //                 left += 2;
        //                 ele.style.left = left + "px";
        //             }
        //         }, 4)
        //     })
        // }
        // move(ball1, 200).then(function () {
        //     return move(ball2, 400);
        // }).then(function () {
        //     return move(ball3, 600);
        // }).then(function () {
        //     alert("promise");
        // })

        // 3.Generator
        // function* m() {
        //     yield move(ball1, 200);
        //     yield move(ball2, 400);
        //     yield move(ball3, 600);
        // }
        // // 利用co方法自動迭代generator
        // function co(it) {
        //     return new Promise(function (resolve, reject) {
        //         function next(d) {
        //             let { value, done } = it.next(d);
        //             if (!done) {
        //                 value.then(function (data) { // 2,txt
        //                     next(data)
        //                 }, reject)
        //             } else {
        //                 resolve(value);
        //             }
        //         }
        //         next();
        //     });
        // }
        // co(m()).then(function () {
        //     alert("generator");
        // })

        // 4.async/await
        // async function a() {
        //     await move(ball1, 200);
        //     await move(ball2, 400);
        //     await move(ball3, 600);
        // }
        // a().then(function () {
        //     alert("async")
        // })
    </script>
</body>

</html>
複製程式碼

相關文章