如何防止JavaScript中异步初始化方法的并行执行?

时间:2020-02-04 15:48:51

标签: javascript async-await

我想在页面加载后立即在JavaScript中预加载搜索索引,这样用户可能不必等待构建它。我知道JavaScript是单线程的,通常不需要锁,但是我不确定以下代码是否足够:

class Index
{
  constructor()
  {
    this.index = null;
  }

  async init()
  {
    this.index = await queryDatabase();
  }

  async search(input)
  {
    if(!this.index) {await this.init();}
    return this.index.query(input);
  }
}

Index index = new index();
index.init(); // preload
myButton.addEventListener("click",console.log(index.search()));

是否会发生用户在init()完成之前单击myButton并两次调用init的情况?如果没有,我该如何最好地防止这种情况发生?

5 个答案:

答案 0 :(得分:5)

init()async,它返回一个Promise,您可以在查询数据库后添加事件侦听器:

class Index {
  constructor() {
    this.index = null;
  }

  async init() {
    this.index = await queryDatabase();
  }

  async search(input) {
    if (!this.index) {
      await this.init();
    }
    return this.index.query(input);
  }
}

Index index = new index();

index.init().then(() => {
  // add the event listener after the init
  myButton.addEventListener("click", function(){
   console.log(index.search()) 
  });
});

答案 1 :(得分:3)

与@Taki相同(赞成),但使用异步/等待语法100%。

IIFE语法:https://jack.ofspades.com/es6-iife-with-fat-arrow-functions/

class Index {
  constructor() {
    this.index = null;
  }

  async init() {
    this.index = await queryDatabase();
  }

  async search(input) {
    // should never happend
    if (this.index === null) {
      await this.init();
    }

    return this.index.query(input);
  }
}

const index = new index();

(async() => {
  await index.init();

  // add the event listener after the init
  myButton.addEventListener('click', console.log(index.search()));
})();

答案 2 :(得分:2)

您也可以在构造函数中将其初始化。

class Index
{
  constructor(){this.index = queryDatabase();}
  async search(input){
    return (await this.index).query(input);
  }
}

或简单地将search更改为普通功能,并假设用户会调用init()并等待它。

class Index
{
  constructor(){this.index = null;}
  async init(){this.index = await queryDatabase();}

  search(input)
  {
    //just assume the `init()` is called.
    return this.index.query(input);
  }
}

*您实际上可以使构造函数返回一个Promise<Index>并像let index = await new Index一样实例化它,但是它没有被广泛使用,并且可能引起混乱。 (一种替代方法是工厂方法)

**还请注意,您的实现并不安全,您可以轻松地多次调用search,而您(错误地)多次调用init。 (这是有效的用例,例如Promise.All(search(A),search(B))

答案 3 :(得分:1)

以上两个答案是“现货”。

我还想提出一个观点,即我不推荐使用search()方法中现在出现的一些代码:

 if(!this.index) {await this.init();}

相反,“在安装尝试使用它的事件侦听器等之前,只需确保已准备好使用 即可。”

答案 4 :(得分:1)

我想知道您是否真的需要ComponentDidMount(){ this.apiReq(); } apiReq(){ request code here.... } 基础架构。如果class就这么简单,那么最好创建Index函数并访问search返回的Promise。该版本可以做到这一点,并通过进度条进行演示,该进度条模拟queryDatabase返回之前的时间。您可以在返回之前单击一次或多次搜索按钮,并且只有在数据可用时才会显示输出。

queryDatabase被对象queryDatabase伪造,返回{foo: 42, bar: 99, baz: 7}"foo""bar"的值。

"baz"
const search = ((index) => 
  async (input) => (await index) .query (input)
) (queryDatabase ())

myButton .addEventListener ('click', async () => {
  log (await search (getTerm ()))
})
.bar {border: 1px solid #666; height: 15px; width: 100%;} .bar .in {animation: fill 5s linear 1; height: 100%; background-color: blue;} @keyframes fill {0% {width: 0%;} 100% {width: 100%;}} select, button {margin-left: 1em;}

您可以使用以下类似方式更明确地说明这一点:

<p><label>Search Term <select id="term"><option>foo</option><option>bar</option><option>baz</option></select></label><button id="myButton">Search</button></p><div class="bar"><div class="in"></div></div><pre id="output">Output:</pre>
<script>
const queryDatabase = () => new Promise ((res) => setTimeout(() => res ({query: key => ({foo: 42, bar: 99, baz: 7})[key]}), 5000))
const myButton = document.getElementById ('myButton')
const term = document.getElementById ('term')
const output = document.getElementById ('output')
const getTerm = () => term.options[term.selectedIndex].value
const log = (msg) => output.innerHTML += '\n' + msg 
</script>

但是无论哪种方式,这似乎都比一个类简单,该类大概只能实例化一次。