Как позволить веб-работнику выполнять несколько задач одновременно? - PullRequest
1 голос
/ 29 мая 2020

Я пытаюсь позволить веб-работнику управлять своим состоянием, одновременно обслуживая несколько асинхронных c запросов.

файл worker.ts

let a =0; //this is my worker's state

let worker=self as unknown as Worker;

worker.onmessage =(e)=>{
    console.log("Rec msg", e.data);

    if(e.data === "+1"){
        setTimeout(()=>{
            a=a+1;
            worker.postMessage(a);
        },3000);
    }else if(e.data=== "+2"){
        setTimeout(()=>{
            a=a+2;
            worker.postMessage(a);
        },1000)
    }
}

А это мой основной файл : main.ts

let w =new Worker("./worker.ts", {type: "module"})

let wf =async (op: string)=>{
    w.postMessage(op);
    return new Promise<any>((res,rej)=>{
        w.onmessage=res;
    });
}

(async()=>{
    let f1 = await wf("+1");
    console.log("f1",f1.data);
})();

(async()=>{
    let f2 = await wf("+2");
    console.log("f2",f2.data);
})()

Возвращается только f2, а f1 теряется. Я использовал таймауты, чтобы имитировать, скажем, некоторую асинхронную c задачу, выполняемую самим работником.

Как мне получить и f1, и f2?

1 Ответ

4 голосов
/ 29 мая 2020

Ваша проблема в том, что вы пытаетесь взять API, основанный на событиях, и использовать его в качестве API на основе Promise, но события могут запускаться несколько раз, а Promise разрешается только один раз.

Связь между Worker и основным потоком осуществляется путем отправки и получения сообщений, но по умолчанию между этими сообщениями нет однозначной связи. Оба конца связи (порты) просто будут складывать входящие сообщения и обрабатывать их последовательно, когда у них будет время.

В вашем коде обработчик worker.onmessage основного потока для f1 был перезаписан вторым вызовом f2 синхронно (одна микрозадача позже, но это все еще синхронно в нашем случае).
Вы можете прикрепить ваше событие с помощью метода addEventListener, по крайней мере, таким образом оно не будет перезаписано. Но даже тогда, когда первое событие message сработает для worker, оба обработчика будут думать, что это собственное сообщение, которое действительно пришло, хотя на самом деле это было сообщение f2. так что это не то, что вам нужно ...

Что вам нужно, так это настроить протокол связи, который позволил бы обоим концам идентифицировать каждую задачу. Вы можете, например, обернуть все данные своих задач объектом, содержащим член .UIID, убедиться, что оба конца переносят свое сообщение таким образом, а затем из основного потока проверить этот UUID для разрешения соответствующего обещания.

Но это может быть немного сложно реализовать и использовать.


Мой личный любимый способ - создать новый MessageChannel за задачу. Если вы не знаете этот API, я предлагаю вам прочитать этот ответ мой, объясняющий основы.

Поскольку мы уверены, что это единственное сообщение, которое придет через этот MessageChannel - это ответ Worker'а на одну задачу, которую мы ему отправили, мы можем ожидать его так же, как Promise.

Все, что нам нужно сделать, это убедиться, что в Worker thread мы отвечать через переданный порт вместо глобальной области.

const url = getWorkerURL();
const w = new Worker(url)

let wf =async (op)=>{
  // we create a new MessageChannel
  const channel = new MessageChannel();
  // we transfer one of its ports to the Worker thread
  w.postMessage(op, [channel.port1]);

  return new Promise((res,rej)=>{
    // we listen for a message from the remaining port of our MessageChannel
    channel.port2.onmessage=res;
  });
}

(async()=>{
    let f1 = await wf("+1");
    console.log("f1",f1.data);
})();

(async()=>{
    let f2 = await wf("+2");
    console.log("f2",f2.data);
})()


// SO only
function getWorkerURL() {
  const elem = document.querySelector( '[type="worker-script"]' );
  const script = elem.textContent;
  const blob = new Blob( [script], { type: "text/javascript" } );
  return URL.createObjectURL( blob );
}
<script type="worker-script">

let a = 0;
const worker = self;

worker.onmessage =(e)=>{
  const port = e.ports[0]; // this is where we will respond
  if(e.data === "+1"){
    setTimeout(()=>{
      a=a+1;
      // we respond through the 'port'
      port.postMessage(a);
    },3000);
  }else if(e.data=== "+2"){
    setTimeout(()=>{
      a=a+2;
      // we respond through the 'port'
      port.postMessage(a);
    },1000)
  }
};
</script>
...