74

An approach to implement async/await in Javascript(even ES3) with nearly zero ov...

 6 years ago
source link: https://github.com/hackwaly/blog/issues/3
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Owner

hackwaly commented Dec 21, 2017

edited

Consider this Javascript, executed in chrome in 2775.01ms on my MBP (please close devtools first before you test it). Why? Because under the hood ES7 use promise to implement await/async, which introduce massive overhead. Call an async function is doggy slow even if it actually doesn't do any async thing.

image
function sleep(t) {
    return new Promise((resolve) => {
        setTimeout(resolve, t);
    });
}
async function small(i, n) {
    if (i === Math.floor(n / 2)) {
        await sleep(1000);
    }
}
async function test(n) {
    let t = performance.now();
    for (let i = 0; i < n; i++) {
        await small(i, n);
    }
    console.log(performance.now() - t);
}
test(5000000);

Now, I'll present you a novel approach to implement async/await. It combines CPS, state machine and exception to reconstruct call stack and avoid closure creation.

The javascript above then can transform to the javascript below. You can test it in your chrome. I'll explain it next time. These code is just prototype for proof. You don't need handwrite your async logic like these. There will be some compilers do it. Thank you!

image
function GetContinuation(callback, outerState) {
    this.callback = callback;
    this.stack = [];
    this.outerState = outerState;
}

function makeContinuation(stack, index) {
    if (index === stack.length) {
        return function () {};
    }
    let func = stack[index];
    let args = stack[index + 1];
    let parent = makeContinuation(stack, index + 2);
    return function () {
        return func.apply(parent, args);;
    };
}

function bind(func, this_, args) {
    return function () {
        return func.apply(this_, args);
    };
}

function callcc(callback, outerState) {
    throw new GetContinuation(callback, outerState);
}

function sleep(timeout, outerState) {
    return callcc(function (continuation) {
        setTimeout(continuation, timeout);
    }, outerState);
}

function small(i, n, outerState) {
    try {
        if (i === Math.floor(n / 2)) {
            sleep(1000, 0);
        }
    } catch (exn) {
        if (exn instanceof GetContinuation) {
            exn.stack.push(small_async, [exn.outerState, i, n]);
            exn.outerState = outerState;
        }
        throw exn;
    }
}

function small_async(state, i, n) {
    try {
        while (true) {
            switch (state) {
            case 0:
                return this();
            }
        }
    } catch (exn) {
        if (exn instanceof GetContinuation) {
            return exn.callback(bind(small_async, this, [exn.outerState, i, n]));
        }
        throw exn;
    }
}

function test(n, outerState) {
    var i;
    var t;
    try {
        t = performance.now();
        i = 0;
        while (i < n) {
            small(i, n, 0);
            i ++;
        }
        console.log(performance.now() - t);
    } catch (exn) {
        if (exn instanceof GetContinuation) {
            exn.stack.push(test_async, [exn.outerState, t, i, n]);
            exn.outerState = outerState;
        }
        throw exn;
    }
}

function test_async(state, t, i, n) {
    try {
        while (true) {
            switch (state) {
            case 0:
                i++;
            case 1:
                if (i >= n) {
                    state = 2;
                    continue;
                }
                small(i, n, 0);
                continue;
            case 2:
                console.log(performance.now() - t);
                return this();
            }
            
        }
    } catch (exn) {
        if (exn instanceof GetContinuation) {
            return exn.callback(bind(test_async, this, [exn.outerState, t, i, n]));
        }
        throw exn;
    }
}

function main() {
    test(5000000, 0);
}

function exec(main) {
    var task;
    task = main;
    while (true) {
        try {
            return task();
        } catch (exn) {
            if (exn instanceof GetContinuation) {
                task = function () {
                    return exn.callback(makeContinuation(exn.stack, 0));
                };
            } else {
                throw exn;
            }
        }
    }
}
exec(main);
Connorelsea, jamOne-, romainl, lyptt, krzkaczor, SergeyKhval, jetfontanilla, gitnik, and artisin reacted with thumbs up emoji

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK