Using javascript `crypto.subtle` in synchronous function

giu*_*pep 8 javascript hash cryptography

In javascript, is it possible to use the browser built-in sha256 hash (https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#Converting_a_digest_to_a_hex_string) inside a synchronous function?

Ideally, I'd like to do something like

String.prototype.sha256 = function() {
    // ...
    return hash
}
Run Code Online (Sandbox Code Playgroud)

I already tried things like (async() => {hash = await digestMessage(message); return hash})(), but I can only get back the promise object.

It seems to me that it might not be possible to achieve what I want, but I thought I'll ask here before giving up. Thanks!

650*_*502 14

我同意 sha256 的异步接口是宇宙中最愚蠢的事情(或者至少当我陷入这种疯狂时感觉如此)。

我的反应是一个javascript 实现,可能比 crypto 提供的慢一些,但不慢并且有一个合理的接口。


Sam*_*aus 10

长话短说

不,不可能在 JavaScript 中将异步函数包装在同步函数中传播结果。请参阅这篇关于各种语言的同步与异步函数的优秀博客文章。结论是,JavaScript 是(许多语言中的)一种,由于语言运行方式的本质,异步函数具有传染性。

异步内置函数是 JS 的救世主

JavaScript 在一个线程上运行。更具体地说,与特定网页相关的所有 JavaScript 都运行在同一个线程上,以保证在任何特定时刻只有一行JS 运行。这使得我们尼安德特人的网络程序员无需编写互斥体和原子操作等同步代码,以免多个线程同时写入同一内​​存并导致数据损坏甚至崩溃。

但是,有点糟糕的是,我们只有一个线程来操作网页上的视觉元素运行各种业务逻辑,例如加密/解密和数据管理。这可能会变得有点慢并损害用户体验。但是异步函数如何解决这个问题呢?采取这个功能:

function syncGenRSAKey() {
    // fancy math stuff...

    return generatedKey;
}
Run Code Online (Sandbox Code Playgroud)

让我们将其设为异步(基于承诺):

function asyncGenRSAKey() {
    return new Promise((resolve, reject) => {
        resolve(syncGenRSAKey());
    });
}
Run Code Online (Sandbox Code Playgroud)

希望您的直觉不会告诉您基于承诺的函数在这里更快。发生的一切是这样的:

  1. 一些代码调用asyncGenRSAKey()
  2. 浏览器运行Promise构造函数
  3. Promise 构造函数立即/同步调用(resolve, reject) => { ... }传递给它的回调函数
  4. 浏览器运行该syncGenRSAKey()函数
  5. 承诺同步履行

我们的代码仍然是完全同步的。我们一无所获。请记住,我们的 JavaScript一次只会运行一行。只要我们的底层密钥生成代码 ( ) 是用 JavaScript 编写的,无论从哪里调用它,它总是会占用主线程的时间。这意味着它将阻止浏览器跳转到其他 JavaScript,即事件处理程序。浏览器还在主线程上渲染页面,因此在运行时它将冻结页面上的几乎所有内容(一些 CSS 动画会被专门渲染)。用户可以将鼠标悬停在按钮上,按钮背景和鼠标光标都不会更新。syncGenRSAKey()genRSAKey()

现在,请返回我的答案的这一部分的小标题。关键词是内置的。内置函数(如下提供的函数)是用浏览器实现者选择的任何语言编写的:C++、Rust 等。这些函数不是JavaScript 引擎crypto.subtle运行,它们是 JavaScript 引擎的一部分。它们可以产生尽可能多的操作系统线程,并在您的计算机在给定时刻空闲的尽可能多(或尽可能少)的 CPU 核心上运行。这意味着密钥生成代码可以并且通常会与一堆 JavaScript 代码和页面呈现选项完全并行运行,然后当密钥准备好并且任何当前运行的 JavaScript 运行完毕时,浏览器将回调到您的 JavaScript ,触发承诺解决(或者如果生成密钥时出现错误则拒绝),然后可以启动链接到生成密钥的任何承诺中的代码。

现在,这对于SHA-256校验和来说真的有必要吗?不。事实上,我自己仍然有一个 GitHub PR,但我一直在推迟,因为我厌倦了承诺一切(其中包括一些非常复杂的 Angular 组件),因为当用户打开模式时我会计算一个该死的哈希值。这是给你的,苏珊娜。

下面是两个精彩的视频,任何阅读 StackOverflow 帖子的人都应该抽出时间观看。除非您充分了解 JavaScript 的同步/异步特性,能够准确地描述代码将如何运行,否则您并没有真正了解JavaScript,并且最终会遇到您无法理解的错误。

Node.js 事件循环:不是单线程的

杰克·阿奇博尔德 (Jake Archibald):循环中 - JSConf.Asia

JavaScript 中async/的澄清await

async关键字await是纯语法糖。它们无法让您完成以前使用老式 Promise 链无法完成的任何操作,就像 Promise 无法让您完成使用良好的 ole 嵌套回调函数无法完成的任何操作一样。async/await只是让你的代码干净 10 倍。最后,与使用嵌套回调相比,Promise 实际上会产生少量的运行时开销,因为 Promise 具有各种状态以方便很好地链接它们并且是堆分配的;async/ await,我听说,可以通过让 JS 引擎更容易地查看异步代码的整体上下文以及使用变量的位置等来取消这个小小的退步,并进行优化。

以下是正确使用async/的一些常见示例。await为了清晰地返回类型,它们是用 TypeScript 编写的,但如果你去掉 s,: Whatever它就会变成 JavaScript。

将同步函数包装在基于 Promise 的 API 中

这实际上很少有必要,但有时您需要您的代码适应第三方代码(如库)所需的接口。

function withoutAsyncAwait(): Promise<number> {
    // Note that the reject callback provided to us by the Promise
    // constructor is rarely useful because the promise will
    // automatically be rejected if our callback throws an error,
    // e.g., if the Math.random() throws an error.
    return new Promise((resolve, reject) => resolve(Math.random()));

    // Could be (ignore the reject callback):
    // return new Promise(resolve => resolve(Math.random()));
}

async function withAsyncAwait(): Promise<number> {
    // If any synchronous code inside an async function throws an
    // error, a promise will still be returned by the async function,
    // but it will be rejected (by far the only desirable behavior).
    // The same is true if an await'ed promise rejects.
    return Math.random();
}
Run Code Online (Sandbox Code Playgroud)

Promise如果您将传统的基于回调的异步函数包装为 Promise,则您不能(并且为什么要)避免构造函数。

function timeout(milliseconds: number): Promise<void> {
    return new Promise(resolve => window.setTimeout(resolve, milliseconds));
}
Run Code Online (Sandbox Code Playgroud)

条件异步步骤

有时您希望在一堆同步代码之前有条件地执行异步操作。在此之前 async/await这意味着您必须复制同步代码或将其全部包装在承诺链中,如果条件不成立,则初始承诺将是无操作。

function doStuffWithoutAsyncAwait1(needToMakeAsyncRequest: boolean): Promise<void> {
    // Might be a no-op promise if we don't need to make a request before sync code
    const promise = needToMakeAsyncRequest ? makeAsyncRequest() : Promise.resolve();

    return promise.then(() => {
        // tons of code omitted here, imagine like 30 lines...
    });
}

function doStuffWithoutAsyncAwait2(needToMakeAsyncRequest: boolean): Promise<void> {
    // Or we can just write the sync code twice, wrapping it in a promise in the branch
    // where we make an async request first. This sucks because our 30 lines of sync
    // code is written twice AND one of the times it is nested/indented inside of both
    // an if-statement and a .then() call
    if (needToMakeAsyncRequest) {
        return makeAsyncRequest().then(() => {
            // tons of code omitted here, imagine like 30 lines...
        });
    }
    
    // tons of code omitted here, imagine like 30 lines...
}

async function cmereAsyncAwaitYouSexyBoiYou(needToMakeAsyncRequest: boolean): Promise<void> {
    if (needToMakeAsyncRequest) {
        // Brings tears to my eyes 
        await makeAsyncRequest();
    }

    // tons of code omitted here, imagine like 30 lines...
}
Run Code Online (Sandbox Code Playgroud)

结合 async/await 和现有的 Promise 机制

async/await不是灵丹妙药。它使得编写一系列异步步骤变得非常干净,但有时我们不仅仅需要一个序列:我们希望多个异步步骤同时运行。

async function takes12SecondsTotal(): Promise<[string, string]> {
    const result1 = await takes7Seconds();
    const result2 = await takes5Seconds(); // will not get here till 1st result is done

    return [result1, result2];
}

async function takes7SecondsTotal(): Promise<[string, string]> {
    // Both inner functions start doing stuff immediately and we just wait for them
    // both to finish
    const [result1, result2] = await Promise.all([
        takes7Seconds(),
        takes5Seconds()
    ]);

    return [result1, result2];
}

function nottttttActuallyyyyyTheSammeeeeIKnowIKnowScrewErrorHandling(): Promise<[string, string]> {
    // We are almost there! However, we just introduced a potential sh!tstorm by reducing down our
    // code and getting rid of async/await: we now have the assumption that both the takes7Seconds()
    // and takes5Seconds() calls DO return promises... but they might have synchronous code and the
    // beginning of them that could throw an error because the author screwed up and then they will
    // blow up SYNCHRONOUSLY in our face and this function will also blow up SYNCHRONOUSLY and it
    // will continue up the call stack until it hits a try-catch or it reaches all the way out and
    // the JS engine stops it and logs it in the dev tools
    return Promise.all([
        takes7Seconds(),
        takes5Seconds()
    ]);

    // Let me illustrate:
    function takes5Seconds(): Promise<string> {
        const now = new Date; // Trivia: you don't need constructor parenthesis if no parameters

        if (now.getDay() === 6 && now.getHours() === 21) { // 9pm on a Saturday
            // Synchronous error
            throw Error("I ain't workin' right now, ok?")
        }

        // Returns a promise, whose rejection will be handled by the promise chain, so an
        // "asynchronous" error (but this function could also throw a synchronous error, you
        // never know)
        return doSomeWork();
    }
}

function thisIsFunctionallyTheSame(): Promise<[string, string]> {
    try {
        return Promise.all([
            takes7Seconds(),
            takes5Seconds()
        ]);
    } catch (err) {
        // catch any synchronous error and gift-wrap it in a promise to protect whoever calls
        // us from a synchronous error explosion
        return Promise.reject(err);
    }
}

async function justBeSmartAndUseAsync(): Promise<[string, string]> {
    // Even though we don't use await at all, async functions act as a stalwart line of defense,
    // stopping any synchronous errors thrown from continuing up the callstack, implicitly
    // catching them and making sure we return a promise NO MATTER WHAT (implicitly does what
    // I did above but the browser probably does it better since async functions are part of the
    // language spec and lots of work has been and will be put into optimizing them)
    return Promise.all([
        takes7Seconds(),
        takes5Seconds()
    ]);
}
Run Code Online (Sandbox Code Playgroud)

我们甚至可能希望同时运行多个异步步骤序列。

async function youCouldBeForgivenForDoingThis(): Promise<void> {
    // Please edit this answer if I'm wrong, but last time I checked, an await keyword holds up
    // the entire expression it's part of--in our case, that means the entire Promise.all(...)
    // expression. The doSomethingUnrelated() will not even start running until writeCode()
    // finishes
    await Promise.all([
        pushCodeToGitHub(await writeCode()),
        doSomethingUnrelated()
    ]);
}

async function armedWithEsotericJSKnowledge(): Promise<void> {
    // Also please note I just await the Promise.all to discard the array of undefined's and
    // return void from our async function
    await Promise.all([
        writeCode().then(code => pushCodeToGitHub(code)),
        doSomethingUnrelated()
    ]);
}
Run Code Online (Sandbox Code Playgroud)

永远不要害怕将承诺存储在变量中,或者根据需要将async箭头函数混合到传统的 承诺链中以获得最智能的代码。.then()

异步函数中返回的深奥废话*t

如果您使用 TypeScript 或者通常熟悉 JS Promise,您可能已经知道在回调内部.then(),您可以返回一个类型T a Promise<T>,并且 Promise 机制在内部完成工作以确保仅将普通数据T传递给下一个.then() 链上。T可以是number或任何其他类型。async函数做同样的事情。错误处理并不那么简单。

function getNumber(): number {
    return 420;
}

async function getNumberAsync(): Promise<number> {
    return getNumber(); // auto-wrap it in a promise cuz we're an async function
}

async function idkJavaScriptButIWantToMakeSureIGetThatNumber(): Promise<number> {
    return await getNumberAsync(); // this IS fine, really
}

async function iKNOWJavaScript(): Promise<number> {
    return getNumberAsync(); // this will NOT return Promise<Promise<number>> because async unwraps it
}

function iLikeToBlowUpRandomly(): Promise<number> {
    if (Math.random() > 0.5) {
        // This is not an async function so this throw clause will NOT get wrapped in a rejected promise
        // and returned pleasantly to the caller
        throw new Error("boom");
    }

    return getNumberAsync();
}

async function iHandleMyProblemsAndAlwaysFulfillMyPromises(): Promise<number> {
    try {
        return iLikeToBlowUpRandomly();
    } catch (err) {
        // This will always catch the "boom" explosions, BUT, if iLikeToBlowUpRandomly() returns a
        // rejected promise, it will sneakily slip through our try-catch because try-catches only
        // catch THROWN errors, and whoever called us will get a bad promise even though we
        // promised (haha) we would only ever return fulfilled promises containing numbers
        return -1;
    }
}

async function iActuallyHandleMyProblemsAndAlwaysFulfillMyPromises(): Promise<number> {
    try {
        // Bam! The normally extraneous await here brings this promise into our pseudo-synchronous
        // async/await code so if it was rejected, it will also trigger our catch branch just like
        // a synchronous error would
        return await iLikeToBlowUpRandomly();
    } catch (err) {
        return 3522047650; // call me if you have job offers  but I'm kinda busy rn and spent way too much time on this
    }
}
Run Code Online (Sandbox Code Playgroud)