主頁 > 企業開發 > JavaScript知識總結 異步編程篇

JavaScript知識總結 異步編程篇

2022-05-07 08:51:37 企業開發

這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

1. 異步編程的實作方式?

JavaScript中的異步機制可以分為以下幾種:

  • 回呼函式 的方式,使用回呼函式的方式有一個缺點是,多個回呼函式嵌套的時候會造成回呼函式地獄,上下兩層的回呼函式間的代碼耦合度太高,不利于代碼的可維護,
  • Promise 的方式,使用 Promise 的方式可以將嵌套的回呼函式作為鏈式呼叫,但是使用這種方法,有時會造成多個 then 的鏈式呼叫,可能會造成代碼的語意不夠明確,
  • generator 的方式,它可以在函式的執行程序中,將函式的執行權轉移出去,在函式外部還可以將執行權轉移回來,當遇到異步函式執行的時候,將函式執行權轉移出去,當異步函式執行完畢時再將執行權給轉移回來,因此在 generator 內部對于異步操作的方式,可以以同步的順序來書寫,使用這種方式需要考慮的問題是何時將函式的控制權轉移回來,因此需要有一個自動執行 generator 的機制,比如說 co 模塊等方式來實作 generator 的自動執行,
  • async 函式 的方式,async 函式是 generator 和 promise 實作的一個自動執行的語法糖,它內部自帶執行器,當函式內部執行到一個 await 陳述句的時候,如果陳述句回傳一個 promise 物件,那么函式將會等待 promise 物件的狀態變為 resolve 后再繼續向下執行,因此可以將異步邏輯,轉化為同步的順序來書寫,并且這個函式可以自動執行,

2. setTimeout、Promise、Async/Await 的區別

(1)setTimeout

console.log('script start')	//1. 列印 script start
setTimeout(function(){
    console.log('settimeout')	// 4. 列印 settimeout
})	// 2. 呼叫 setTimeout 函式,并定義其完成后執行的回呼函式
console.log('script end')	//3. 列印 script start
// 輸出順序:script start->script end->settimeout

(2)Promise

Promise本身是同步的立即執行函式, 當在executor中執行resolve或者reject的時候, 此時是異步操作, 會先執行then/catch等,當主堆疊完成后,才會去呼叫resolve/reject中存放的方法執行,列印p的時候,是列印的回傳結果,一個Promise實體,

console.log('script start')
let promise1 = new Promise(function (resolve) {
    console.log('promise1')
    resolve()
    console.log('promise1 end')
}).then(function () {
    console.log('promise2')
})
setTimeout(function(){
    console.log('settimeout')
})
console.log('script end')
// 輸出順序: script start->promise1->promise1 end->script end->promise2->settimeout

當JS主執行緒執行到Promise物件時:

  • promise1.then() 的回呼就是一個 task
  • promise1 是 resolved或rejected: 那這個 task 就會放入當前事件回圈回合的 microtask queue
  • promise1 是 pending: 這個 task 就會放入 事件回圈的未來的某個(可能下一個)回合的 microtask queue 中
  • setTimeout 的回呼也是個 task ,它會被放入 macrotask queue 即使是 0ms 的情況

(3)async/await

async function async1(){
   console.log('async1 start');
    await async2();
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}
console.log('script start');
async1();
console.log('script end')
// 輸出順序:script start->async1 start->async2->script end->async1 end

async 函式回傳一個 Promise 物件,當函式執行的時候,一旦遇到 await 就會先回傳,等到觸發的異步操作完成,再執行函式體內后面的陳述句,可以理解為,是讓出了執行緒,跳出了 async 函式體,

例如:

async function func1() {
    return 1
}
console.log(func1())

func1的運行結果其實就是一個Promise物件,因此也可以使用then來處理后續邏輯,

func1().then(res => {
    console.log(res);  // 30
})

await的含義為等待,也就是 async 函式需要等待await后的函式執行完成并且有了回傳結果(Promise物件)之后,才能繼續執行下面的代碼,await通過回傳一個Promise物件來實作同步的效果,

3. 對Promise的理解

Promise是異步編程的一種解決方案,它是一個物件,可以獲取異步操作的訊息,他的出現大大改善了異步編程的困境,避免了地獄回呼,它比傳統的解決方案回呼函式和事件更合理和更強大,

所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果,從語法上說,Promise 是一個物件,從它可以獲取異步操作的訊息,Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處理,

(1)Promise的實體有三個狀態:

  • Pending(進行中)
  • Resolved(已完成)
  • Rejected(已拒絕)

當把一件事情交給promise時,它的狀態就是Pending,任務完成了狀態就變成了Resolved、沒有完成失敗了就變成了Rejected,

(2)Promise的實體有兩個程序

  • pending -> fulfilled : Resolved(已完成)
  • pending -> rejected:Rejected(已拒絕)

注意:一旦從進行狀態變成為其他狀態就永遠不能更改狀態了,

Promise的特點:

  • 物件的狀態不受外界影響,promise物件代表一個異步操作,有三種狀態,pending(進行中)、fulfilled(已成功)、rejected(已失敗),只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態,這也是promise這個名字的由來——“承諾”;
  • 一旦狀態改變就不會再變,任何時候都可以得到這個結果,promise物件的狀態改變,只有兩種可能:從pending變為fulfilled,從pending變為rejected,這時就稱為resolved(已定型),如果改變已經發生了,你再對promise物件添加回呼函式,也會立即得到這個結果,這與事件(event)完全不同,事件的特點是:如果你錯過了它,再去監聽是得不到結果的,

Promise的缺點:

  • 無法取消Promise,一旦新建它就會立即執行,無法中途取消,
  • 如果不設定回呼函式,Promise內部拋出的錯誤,不會反應到外部,
  • 當處于pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成),

總結:

Promise 物件是異步編程的一種解決方案,最早由社區提出,Promise 是一個建構式,接收一個函式作為引數,回傳一個 Promise 實體,一個 Promise 實體有三種狀態,分別是pending、resolved 和 rejected,分別代表了進行中、已成功和已失敗,實體的狀態只能由 pending 轉變 resolved 或者rejected 狀態,并且狀態一經改變,就凝固了,無法再被改變了,

狀態的改變是通過 resolve() 和 reject() 函式來實作的,可以在異步操作結束后呼叫這兩個函式改變 Promise 實體的狀態,它的原型上定義了一個 then 方法,使用這個 then 方法可以為兩個狀態的改變注冊回呼函式,這個回呼函式屬于微任務,會在本輪事件回圈的末尾執行,

注意:在構造 Promise 的時候,建構式內部的代碼是立即執行的

4. Promise的基本用法

(1)創建Promise物件

Promise物件代表一個異步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗),

Promise建構式接受一個函式作為引數,該函式的兩個引數分別是resolvereject

const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 異步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

一般情況下都會使用new Promise()來創建promise物件,但是也可以使用promise.resolvepromise.reject這兩個方法:

  • Promise.resolve

Promise.resolve(value)的回傳值也是一個promise物件,可以對回傳值進行.then呼叫,代碼如下:

Promise.resolve(11).then(function(value){
  console.log(value); // 列印出11
});

resolve(11)代碼中,會讓promise物件進入確定(resolve狀態),并將引數11傳遞給后面的then所指定的onFulfilled 函式;

創建promise物件可以使用new Promise的形式創建物件,也可以使用Promise.resolve(value)的形式創建promise物件;

  • Promise.reject

Promise.reject 也是new Promise的快捷形式,也創建一個promise物件,代碼如下

Promise.reject(new Error(“我錯了,請原諒俺!!”));

就是下面的代碼new Promise的簡單形式:

new Promise(function(resolve,reject){
   reject(new Error("我錯了,請原諒俺!!"));
});

下面是使用resolve方法和reject方法:

function testPromise(ready) {
  return new Promise(function(resolve,reject){
    if(ready) {
      resolve("hello world");
    }else {
      reject("No thanks");
    }
  });
};
// 方法呼叫
testPromise(true).then(function(msg){
  console.log(msg);
},function(error){
  console.log(error);
});

上面的代碼的含義是給testPromise方法傳遞一個引數,回傳一個promise物件,如果為true的話,那么呼叫promise物件中的resolve()方法,并且把其中的引數傳遞給后面的then第一個函式內,因此列印出 “hello world”, 如果為false的話,會呼叫promise物件中的reject()方法,則會進入then的第二個函式內,會列印No thanks

(2)Promise方法

Promise有五個常用的方法:then()、catch()、all()、race()、finally,下面就來看一下這些方法,

  1. then()

當Promise執行的內容符合成功條件時,呼叫resolve函式,失敗就呼叫reject函式,Promise創建完了,那該如何呼叫呢?

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then方法可以接受兩個回呼函式作為引數,第一個回呼函式是Promise物件的狀態變為resolved時呼叫,第二個回呼函式是Promise物件的狀態變為rejected時呼叫,其中第二個引數可以省略,

then方法回傳的是一個新的Promise實體(不是原來那個Promise實體),因此可以采用鏈式寫法,即then方法后面再呼叫另一個then方法,

當要寫有順序的異步事件時,需要串行時,可以這樣寫:

let promise = new Promise((resolve,reject)=>{
    ajax('first').success(function(res){
        resolve(res);
    })
})
promise.then(res=>{
    return new Promise((resovle,reject)=>{
        ajax('second').success(function(res){
            resolve(res)
        })
    })
}).then(res=>{
    return new Promise((resovle,reject)=>{
        ajax('second').success(function(res){
            resolve(res)
        })
    })
}).then(res=>{
    
})

那當要寫的事件沒有順序或者關系時,還如何寫呢?可以使用all 方法來解決,

2. catch()

Promise物件除了有then方法,還有一個catch方法,該方法相當于then方法的第二個引數,指向reject的回呼函式,不過catch方法還有一個作用,就是在執行resolve回呼函式時,如果出現錯誤,拋出例外,不會停止運行,而是進入catch方法中,

p.then((data) => {
     console.log('resolved',data);
},(err) => {
     console.log('rejected',err);
     }
); 
p.then((data) => {
    console.log('resolved',data);
}).catch((err) => {
    console.log('rejected',err);
});

3. all()

all方法可以完成并行任務, 它接收一個陣列,陣列的每一項都是一個promise物件,當陣列中所有的promise的狀態都達到resolved的時候,all方法的狀態就會變成resolved,如果有一個狀態變成了rejected,那么all方法的狀態就會變成rejected

javascript
let promise1 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
       resolve(1);
	},2000)
});
let promise2 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
       resolve(2);
	},1000)
});
let promise3 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
       resolve(3);
	},3000)
});
Promise.all([promise1,promise2,promise3]).then(res=>{
    console.log(res);
    //結果為:[1,2,3] 
})

呼叫all方法時的結果成功的時候是回呼函式的引數也是一個陣列,這個陣列按順序保存著每一個promise物件resolve執行時的值,

(4)race()

race方法和all一樣,接受的引數是一個每項都是promise的陣列,但是與all不同的是,當最先執行完的事件執行完之后,就直接回傳該promise物件的值,如果第一個promise物件狀態變成resolved,那自身的狀態變成了resolved;反之第一個promise變成rejected,那自身狀態就會變成rejected

let promise1 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
       reject(1);
	},2000)
});
let promise2 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
       resolve(2);
	},1000)
});
let promise3 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
       resolve(3);
	},3000)
});
Promise.race([promise1,promise2,promise3]).then(res=>{
	console.log(res);
	//結果:2
},rej=>{
    console.log(rej)};
)

那么race方法有什么實際作用呢?當要做一件事,超過多長時間就不做了,可以用這個方法來解決:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})

5. finally()

finally方法用于指定不管 Promise 物件最后狀態如何,都會執行的操作,該方法是 ES2018 引入標準的,

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

上面代碼中,不管promise最后的狀態,在執行完thencatch指定的回呼函式以后,都會執行finally方法指定的回呼函式,

下面是一個例子,服務器使用 Promise 處理請求,然后使用finally方法關掉服務器,

server.listen(port)
  .then(function () {
    // ...
  })
  .finally(server.stop);

finally方法的回呼函式不接受任何引數,這意味著沒有辦法知道,前面的 Promise 狀態到底是fulfilled還是rejected,這表明,finally方法里面的操作,應該是與狀態無關的,不依賴于 Promise 的執行結果,finally本質上是then方法的特例:

promise
.finally(() => {
  // 陳述句
});
// 等同于
promise
.then(
  result => {
    // 陳述句
    return result;
  },
  error => {
    // 陳述句
    throw error;
  }
);

上面代碼中,如果不使用finally方法,同樣的陳述句需要為成功和失敗兩種情況各寫一次,有了finally方法,則只需要寫一次,

5. Promise解決了什么問題

在作業中經常會碰到這樣一個需求,比如我使用ajax發一個A請求后,成功后拿到資料,需要把資料傳給B請求;那么需要如下撰寫代碼:

let fs = require('fs')
fs.readFile('./a.txt','utf8',function(err,data){
  fs.readFile(data,'utf8',function(err,data){
    fs.readFile(data,'utf8',function(err,data){
      console.log(data)
    })
  })
})

上面的代碼有如下缺點:

  • 后一個請求需要依賴于前一個請求成功后,將資料往下傳遞,會導致多個ajax請求嵌套的情況,代碼不夠直觀,
  • 如果前后兩個請求不需要傳遞引數的情況下,那么后一個請求也需要前一個請求成功后再執行下一步操作,這種情況下,那么也需要如上撰寫代碼,導致代碼不夠直觀,

Promise出現之后,代碼變成這樣:

let fs = require('fs')
function read(url){
  return new Promise((resolve,reject)=>{
    fs.readFile(url,'utf8',function(error,data){
      error && reject(error)
      resolve(data)
    })
  })
}
read('./a.txt').then(data=https://www.cnblogs.com/smileZAZ/p/>{
  return read(data) 
}).then(data=>{
  return read(data)  
}).then(data=>{
  console.log(data)
})

這樣代碼看起了就簡潔了很多,解決了地獄回呼的問題,

6. Promise.all和Promise.race的區別的使用場景

(1)Promise.all

Promise.all可以將多個Promise實體包裝成一個新的Promise實體,同時,成功和失敗的回傳值是不同的,成功的時候回傳的是一個結果陣列,而失敗的時候則回傳最先被reject失敗狀態的值

Promise.all中傳入的是陣列,回傳的也是是陣列,并且會將進行映射,傳入的promise物件回傳的值是按照順序在陣列中排列的,但是注意的是他們執行的順序并不是按照順序的,除非可迭代物件為空,

需要注意,Promise.all獲得的成功結果的陣列里面的資料順序和Promise.all接收到的陣列順序是一致的,這樣當遇到發送多個請求并根據請求順序獲取和使用資料的場景,就可以使用Promise.all來解決,

(2)Promise.race

顧名思義,Promse.race就是賽跑的意思,意思就是說,Promise.race([p1, p2, p3])里面哪個結果獲得的快,就回傳那個結果,不管結果本身是成功狀態還是失敗狀態,當要做一件事,超過多長時間就不做了,可以用這個方法來解決:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})

7. 對async/await 的理解

async/await其實是Generator 的語法糖,它能實作的效果都能用then鏈來實作,它是為優化then鏈而開發出來的,從字面上來看,async是“異步”的簡寫,await則為等待,所以很好理解async 用于申明一個 function 是異步的,而 await 用于等待一個異步方法執行完成,當然語法上強制規定await只能出現在asnyc函式中,先來看看async函式回傳了什么: 

async function testAsy(){
   return 'hello world';
}
let result = testAsy(); 
console.log(result)

所以,async 函式回傳的是一個 Promise 物件,async 函式(包含函式陳述句、函式運算式、Lambda運算式)會回傳一個 Promise 物件,如果在函式中 return 一個直接量,async 會把這個直接量通過 Promise.resolve() 封裝成 Promise 物件,

async 函式回傳的是一個 Promise 物件,所以在最外層不能用 await 獲取其回傳值的情況下,當然應該用原來的方式:then() 鏈來處理這個 Promise 物件,就像這樣:

async function testAsy(){
   return 'hello world'
}
let result = testAsy() 
console.log(result)
result.then(v=>{
    console.log(v)   // hello world
})

那如果 async 函式沒有回傳值,又該如何?很容易想到,它會回傳 Promise.resolve(undefined)

聯想一下 Promise 的特點——無等待,所以在沒有 await 的情況下執行 async 函式,它會立即執行,回傳一個 Promise 物件,并且,絕不會阻塞后面的陳述句,這和普通回傳 Promise 物件的函式并無二致,

注意:Promise.resolve(x) 可以看作是 new Promise(resolve => resolve(x)) 的簡寫,可以用于快速封裝字面量物件或其他物件,將其封裝成 Promise 實體,

8. await 到底在等啥?

await 在等待什么呢?一般來說,都認為 await 是在等待一個 async 函式完成,不過按語法說明,await 等待的是一個運算式,這個運算式的計算結果是 Promise 物件或者其它值(換句話說,就是沒有特殊限定),

因為 async 函式回傳一個 Promise 物件,所以 await 可以用于等待一個 async 函式的回傳值——這也可以說是 await 在等 async 函式,但要清楚,它等的實際是一個回傳值,注意到 await 不僅僅用于等 Promise 物件,它可以等任意運算式的結果,所以,await 后面實際是可以接普通函式呼叫或者直接量的,所以下面這個示例完全可以正確運行:

function getSomething() {
    return "something";
}
async function testAsync() {
    return Promise.resolve("hello async");
}
async function test() {
    const v1 = await getSomething();
    const v2 = await testAsync();
    console.log(v1, v2);
}
test();

await 運算式的運算結果取決于它等的是什么,

  • 如果它等到的不是一個 Promise 物件,那 await 運算式的運算結果就是它等到的東西,
  • 如果它等到的是一個 Promise 物件,await 就忙起來了,它會阻塞后面的代碼,等著 Promise 物件 resolve,然后得到 resolve 的值,作為 await 運算式的運算結果,

來看一個例子:

function testAsy(x){
   return new Promise(resolve=>{setTimeout(() => {
       resolve(x);
     }, 3000)
    }
   )
}
async function testAwt(){    
  let result =  await testAsy('hello world');
  console.log(result);    // 3秒鐘之后出現hello world
  console.log('cuger')   // 3秒鐘之后出現cug
}
testAwt();
console.log('cug')  //立即輸出cug

這就是 await 必須用在 async 函式中的原因,async 函式呼叫不會造成阻塞,它內部所有的阻塞都被封裝在一個 Promise 物件中異步執行,await暫停當前async的執行,所以'cug''最先輸出,hello world'和‘cuger’是3秒鐘后同時出現的,

9. async/await的優勢

單一的 Promise 鏈并不能發現 async/await 的優勢,但是,如果需要處理由多個 Promise 組成的 then 鏈的時候,優勢就能體現出來了(很有意思,Promise 通過 then 鏈來解決多層回呼的問題,現在又用 async/await 來進一步優化它),

假設一個業務,分多個步驟完成,每個步驟都是異步的,而且依賴于上一個步驟的結果,仍然用 setTimeout 來模擬異步操作:

/**
 * 傳入引數 n,表示這個函式執行的時間(毫秒)
 * 執行的結果是 n + 200,這個值將用于下一步驟
 */
function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}
function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}
function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}
function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}

現在用 Promise 方式來實作這三個步驟的處理:

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms

輸出結果 resultstep3() 的引數 700 + 200 = 900doIt() 順序執行了三個步驟,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 計算的結果一致,

如果用 async/await 來實作呢,會是這樣:

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}
doIt();

結果和之前的 Promise 實作是一樣的,但是這個代碼看起來是不是清晰得多,幾乎跟同步代碼一樣

10. async/await對比Promise的優勢

  • 代碼讀起來更加同步,Promise雖然擺脫了回呼地獄,但是then的鏈式調?也會帶來額外的閱讀負擔
  • Promise傳遞中間值?常麻煩,?async/await?乎是同步的寫法,?常優雅
  • 錯誤處理友好,async/await可以?成熟的try/catchPromise的錯誤捕獲?常冗余
  • 除錯友好,Promise的除錯很差,由于沒有代碼塊,你不能在?個回傳運算式的箭頭函式中設定斷點,如果你在?個.then代碼塊中使?除錯器的步進(step-over)功能,除錯器并不會進?后續的.then代碼塊,因為除錯器只能跟蹤同步代碼的每?步,

11. async/await 如何捕獲例外

async function fn(){
    try{
        let a = await Promise.reject('error')
    }catch(error){
        console.log(error)
    }
}

12. 并發與并行的區別?

  • 并發是宏觀概念,我分別有任務 A 和任務 B,在一段時間內通過任務間的切換完成了這兩個任務,這種情況就可以稱之為并發,
  • 并行是微觀概念,假設 CPU 中存在兩個核心,那么我就可以同時完成任務 A、B,同時完成多個任務的情況就可以稱之為并行,

13. 什么是回呼函式?回呼函式有什么缺點?如何解決回呼地獄問題?

以下代碼就是一個回呼函式的例子:

ajax(url, () => {
    // 處理邏輯
})

回呼函式有一個致命的弱點,就是容易寫出回呼地獄(Callback hell),假設多個請求存在依賴性,可能會有如下代碼:

ajax(url, () => {
    // 處理邏輯
    ajax(url1, () => {
        // 處理邏輯
        ajax(url2, () => {
            // 處理邏輯
        })
    })
})

以上代碼看起來不利于閱讀和維護,當然,也可以把函式分開來寫:

function firstAjax() {
  ajax(url1, () => {
    // 處理邏輯
    secondAjax()
  })
}
function secondAjax() {
  ajax(url2, () => {
    // 處理邏輯
  })
}
ajax(url, () => {
  // 處理邏輯
  firstAjax()
})

以上的代碼雖然看上去利于閱讀了,但是還是沒有解決根本問題,回呼地獄的根本問題就是:

  1. 嵌套函式存在耦合性,一旦有所改動,就會牽一發而動全身
  2. 嵌套函式一多,就很難處理錯誤

當然,回呼函式還存在著別的幾個缺點,比如不能使用 try catch 捕獲錯誤,不能直接 return

14. setTimeout、setInterval、requestAnimationFrame 各有什么特點?

異步編程當然少不了定時器了,常見的定時器函式有 setTimeoutsetIntervalrequestAnimationFrame,最常用的是setTimeout,很多人認為 setTimeout 是延時多久,那就應該是多久后執行,

其實這個觀點是錯誤的,因為 JS 是單執行緒執行的,如果前面的代碼影響了性能,就會導致 setTimeout 不會按期執行,當然了,可以通過代碼去修正 setTimeout,從而使定時器相對準確:

let period = 60 * 1000 * 60 * 2
let startTime = new Date().getTime()
let count = 0
let end = new Date().getTime() + period
let interval = 1000
let currentInterval = interval
function loop() {
  count++
  // 代碼執行所消耗的時間
  let offset = new Date().getTime() - (startTime + count * interval);
  let diff = end - new Date().getTime()
  let h = Math.floor(diff / (60 * 1000 * 60))
  let hdiff = diff % (60 * 1000 * 60)
  let m = Math.floor(hdiff / (60 * 1000))
  let mdiff = hdiff % (60 * 1000)
  let s = mdiff / (1000)
  let sCeil = Math.ceil(s)
  let sFloor = Math.floor(s)
  // 得到下一次回圈所消耗的時間
  currentInterval = interval - offset 
  console.log('時:'+h, '分:'+m, '毫秒:'+s, '秒向上取整:'+sCeil, '代碼執行時間:'+offset, '下次回圈間隔'+currentInterval) 
  setTimeout(loop, currentInterval)
}
setTimeout(loop, currentInterval)

接下來看 setInterval,其實這個函式作用和 setTimeout 基本一致,只是該函式是每隔一段時間執行一次回呼函式,

通常來說不建議使用 setInterval,第一,它和 setTimeout 一樣,不能保證在預期的時間執行任務,第二,它存在執行累積的問題,請看以下偽代碼

function demo() {
  setInterval(function(){
    console.log(2)
  },1000)
  sleep(2000)
}
demo()

以上代碼在瀏覽器環境中,如果定時器執行程序中出現了耗時操作,多個回呼函式會在耗時操作結束以后同時執行,這樣可能就會帶來性能上的問題,

如果有回圈定時器的需求,其實完全可以通過 requestAnimationFrame 來實作:

function setInterval(callback, interval) {
  let timer
  const now = Date.now
  let startTime = now()
  let endTime = startTime
  const loop = () => {
    timer = window.requestAnimationFrame(loop)
    endTime = now()
    if (endTime - startTime >= interval) {
      startTime = endTime = now()
      callback(timer)
    }
  }
  timer = window.requestAnimationFrame(loop)
  return timer
}
let a = 0
setInterval(timer => {
  console.log(1)
  a++
  if (a === 3) cancelAnimationFrame(timer)
}, 1000)

首先 requestAnimationFrame 自帶函式節流功能,基本可以保證在 16.6 毫秒內只執行一次(不掉幀的情況下),并且該函式的延時效果是精確的,沒有其他定時器時間不準的問題,當然你也可以通過該函式來實作 setTimeout

如果對您有所幫助,歡迎您點個關注,我會定時更新技術檔案,大家一起討論學習,一起進步,

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/469847.html

標籤:JavaScript

上一篇:實戰 target 選擇器,解放生產力!

下一篇:函式宣告和函式運算式的區別

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more