如何阻塞正在被调用的方法,等前一次调用结束后才执行

如何写一个阻塞的 javascript 方法,如果有其他正在调用则被阻塞直到前一次方法调用完成才执行。

var foobar = function(){
    setTimeout(function(){ console.log("hello"); },2000);
}

for(let i = 0;i< 1000; i++){
    foobar();
}

比如我想让上面的for循环每2000ms才执行一次循环

阅读 3.8k
3 个回答

你的意思就是js实现睡眠吧

同步版本

var foobar = function(){
    console.log("hello")
}
const sleepSync = (ms) => {
  const end = new Date().getTime() + ms;
  while (new Date().getTime() < end) {}
}
for(let i = 0;i< 1000; i++){
    foobar();
    sleepSync(2000);
}

异步版本

const sleep = (ms) =>
  new Promise(resolve => setTimeout(resolve, ms));

const sayHello = async() => {
  for(let i = 0;i< 1000; i++){
    foobar();
    await sleep(2000);
  }
};

sayHello(); 

一般程序不需要主动阻塞,这样会使程序变得低效,不能充分利用cpu时间,阻塞的场景一般都是IO调用如网络请求等。如果需要主动阻塞,通常是逻辑设计有问题。

如果是需要顺序调用接口,前一次接口调用返回后再调用下一个接口,建议可以这样改逻辑:

  • await写法
(async () => {
    let r1 = await fetch('http://api.example.com/api1')
    // 处理你的第一次请求
    let r2 = await fetch('http://api.example.com/api2')
    // 处理你的第二次请求
})()
  • Promise写法
fetch('http://api.example.com/api1').then(r => {
    // 处理你的第一次请求
    
    return fetch('http://api.example.com/api2')
}).then(r => {
   // 处理你的第二次请求
})
  • callback写法
fetch('http://api.example.com/api1', r1 => {
    // 处理你的第一次请求
    fetch('http://api.example.com/api2', r2 => {
        // 处理你的第二次请求
    })
})

如果不关系请求返回,可以将请求做成发送队列,调用的位置不要直接发请求,而是将请求和请求参数作为一个任务加入到发送队列中,然后在队列中完成发送

看了楼上的评论区才知道你的需求。。。正确的提问方式应该是把这个需求描述放到问题中去,这样回答者能够给你更好、更直接的解决方案。
比如你这个单信道复用的实现,最好的方案就是设置一个缓冲队列,每次有数据要发送时,并不是直接去调用发送数据的方法,而是把数据写入缓冲队列,等前一个请求完成的时候,把队列最前面的数据发送出去。
以等待2秒为例,差不多就是这样实现:

const addTask = (function(){
    const taskQueue = [];
    let pendingFlag = false;
    
    function handler(task){
        setTimeout(() => {
            task();
            consumer();
        }, 2e3);
    }
    
    function consumer(){
        if(!taskQueue.length){
            pendingFlag = false;
            return
        }
        handler(taskQueue.shift());
    }
    
    return function addTask(task){
        taskQueue.push(task);
        if(!pendingFlag) {
            pendingFlag = true;
            consumer();
        }
    }
})();

for(let i = 0;i< 10; i++){
    addTask(() => console.log(`这是第${i + 1}个任务`));
}

但上面这是一个定制性的方案,每管理一种任务就需要完整地抄一遍,为了避免这种重复,我们可以写一个更加通用的解决方案:

function queueGen(processor){
    const taskQueue = [];
    let pendingFlag = false;
    
    async function handler([param, callback, errorHandler]){
        try{
            callback(await processor(param));
        } catch(err) {
            if(errorHandler){
                errorHandler(err)
            }
        }
        consumer();
    }
    
    function consumer(){
        if(!taskQueue.length){
            pendingFlag = false;
            return
        }
        handler(taskQueue.shift());
    }
    
    return function addTask(callback, param, errorHandler){
        taskQueue.push([param, callback, errorHandler]);
        if(!pendingFlag) {
            pendingFlag = true;
            consumer();
        }
    }
}

const addTask = queueGen(function asyncTimeOut(delay = 1e3){
    return new Promise(function(resolve, reject){
        setTimeout(resolve, delay)
    });
});

for(let i = 0;i< 10; i++){
    const delay = (10 % i) * 1e8
    addTask(() => console.log(`第${i + 1}个任务,延迟了${delay}毫秒`), delay);
}

剩下的任务就是把你的通信接口写成 Promise 风格交给 queueGen 处理一下,然后随时随地 addTask 就行了。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏