นี่คือผู้อธิบายสำหรับข้อเสนอ API ที่สังเกตได้สำหรับการจัดการเหตุการณ์ที่มีหลักการและการตกแต่งมากขึ้น
EventTarget ข้อเสนอนี้เพิ่มวิธี .when() ไปยัง EventTarget ที่กลายเป็น addEventListener() ; โดยเฉพาะจะส่งคืน Observable ใหม่ที่เพิ่มผู้ฟังเหตุการณ์ใหม่ไปยังเป้าหมายเมื่อเรียกใช้วิธี subscribe() ผู้ที่สังเกตได้เรียกว่าตัวจัดการ next() ของสมาชิกในแต่ละเหตุการณ์
การจัดการเหตุการณ์ที่สังเกตได้เปลี่ยนเหตุการณ์การกรองและการสิ้นสุดลงในกระแสที่ชัดเจนและประกาศซึ่งง่ายกว่าที่จะเข้าใจและเขียนได้ง่ายกว่ารุ่นที่จำเป็นของวันนี้ซึ่งมักจะต้องมีการโทรซ้อนกันไปยัง addEventListener()
// Filtering and mapping:
element
. when ( 'click' )
. filter ( ( e ) => e . target . matches ( '.foo' ) )
. map ( ( e ) => ( { x : e . clientX , y : e . clientY } ) )
. subscribe ( { next : handleClickAtPoint } ) ; // Automatic, declarative unsubscription via the takeUntil method:
element . when ( 'mousemove' )
. takeUntil ( document . when ( 'mouseup' ) )
. subscribe ( { next : e => … } ) ;
// Since reduce and some other terminators return promises, they also play
// well with async functions:
await element . when ( 'mousemove' )
. takeUntil ( element . when ( 'mouseup' ) )
. reduce ( ( soFar , e ) => … ) ; // Imperative
const controller = new AbortController ( ) ;
element . addEventListener ( 'mousemove' , e => {
console . log ( e ) ;
element . addEventListener ( 'mouseup' , e => {
controller . abort ( ) ;
} ) ;
} , { signal : controller . signal } ) ; การติดตามการคลิกลิงก์ทั้งหมดภายในคอนเทนเนอร์ (ตัวอย่าง):
container
. when ( 'click' )
. filter ( ( e ) => e . target . closest ( 'a' ) )
. subscribe ( {
next : ( e ) => {
// …
} ,
} ) ; ค้นหาพิกัดสูงสุด y ในขณะที่เมาส์ถูกเก็บไว้ (ตัวอย่าง):
const maxY = await element
. when ( 'mousemove' )
. takeUntil ( element . when ( 'mouseup' ) )
. map ( ( e ) => e . clientY )
. reduce ( ( soFar , y ) => Math . max ( soFar , y ) , 0 ) ; มัลติเพล็กซ์ WebSocket เช่นข้อความการสมัครสมาชิกจะส่งการเชื่อมต่อและข้อความที่ไม่ได้สมัครจะส่งไปยังเซิร์ฟเวอร์เมื่อผู้ใช้ยกเลิกการสมัคร
const socket = new WebSocket ( 'wss://example.com' ) ;
function multiplex ( { startMsg , stopMsg , match } ) {
if ( socket . readyState !== WebSocket . OPEN ) {
return socket
. when ( 'open' )
. flatMap ( ( ) => multiplex ( { startMsg , stopMsg , match } ) ) ;
} else {
socket . send ( JSON . stringify ( startMsg ) ) ;
return socket
. when ( 'message' )
. filter ( match )
. takeUntil ( socket . when ( 'close' ) )
. takeUntil ( socket . when ( 'error' ) )
. map ( ( e ) => JSON . parse ( e . data ) )
. finally ( ( ) => {
socket . send ( JSON . stringify ( stopMsg ) ) ;
} ) ;
}
}
function streamStock ( ticker ) {
return multiplex ( {
startMsg : { ticker , type : 'sub' } ,
stopMsg : { ticker , type : 'unsub' } ,
match : ( data ) => data . ticker === ticker ,
} ) ;
}
const googTrades = streamStock ( 'GOOG' ) ;
const nflxTrades = streamStock ( 'NFLX' ) ;
const googController = new AbortController ( ) ;
googTrades . subscribe ( { next : updateView } , { signal : googController . signal } ) ;
nflxTrades . subscribe ( { next : updateView , ... } ) ;
// And the stream can disconnect later, which
// automatically sends the unsubscription message
// to the server.
googController . abort ( ) ; // Imperative
function multiplex ( { startMsg , stopMsg , match } ) {
const start = ( callback ) => {
const teardowns = [ ] ;
if ( socket . readyState !== WebSocket . OPEN ) {
const openHandler = ( ) => start ( { startMsg , stopMsg , match } ) ( callback ) ;
socket . addEventListener ( 'open' , openHandler ) ;
teardowns . push ( ( ) => {
socket . removeEventListener ( 'open' , openHandler ) ;
} ) ;
} else {
socket . send ( JSON . stringify ( startMsg ) ) ;
const messageHandler = ( e ) => {
const data = JSON . parse ( e . data ) ;
if ( match ( data ) ) {
callback ( data ) ;
}
} ;
socket . addEventListener ( 'message' , messageHandler ) ;
teardowns . push ( ( ) => {
socket . send ( JSON . stringify ( stopMsg ) ) ;
socket . removeEventListener ( 'message' , messageHandler ) ;
} ) ;
}
const finalize = ( ) => {
teardowns . forEach ( ( t ) => t ( ) ) ;
} ;
socket . addEventListener ( 'close' , finalize ) ;
teardowns . push ( ( ) => socket . removeEventListener ( 'close' , finalize ) ) ;
socket . addEventListener ( 'error' , finalize ) ;
teardowns . push ( ( ) => socket . removeEventListener ( 'error' , finalize ) ) ;
return finalize ;
} ;
return start ;
}
function streamStock ( ticker ) {
return multiplex ( {
startMsg : { ticker , type : 'sub' } ,
stopMsg : { ticker , type : 'unsub' } ,
match : ( data ) => data . ticker === ticker ,
} ) ;
}
const googTrades = streamStock ( 'GOOG' ) ;
const nflxTrades = streamStock ( 'NFLX' ) ;
const unsubGoogTrades = googTrades ( updateView ) ;
const unsubNflxTrades = nflxTrades ( updateView ) ;
// And the stream can disconnect later, which
// automatically sends the unsubscription message
// to the server.
unsubGoogTrades ( ) ; ที่นี่เราใช้ประโยชน์จากการสังเกตเพื่อให้ตรงกับรหัสลับซึ่งเป็นรูปแบบของคีย์ที่ผู้ใช้อาจตีขณะใช้แอพ:
const pattern = [
'ArrowUp' ,
'ArrowUp' ,
'ArrowDown' ,
'ArrowDown' ,
'ArrowLeft' ,
'ArrowRight' ,
'ArrowLeft' ,
'ArrowRight' ,
'b' ,
'a' ,
'b' ,
'a' ,
'Enter' ,
] ;
const keys = document . when ( 'keydown' ) . map ( e => e . key ) ;
keys
. flatMap ( firstKey => {
if ( firstKey === pattern [ 0 ] ) {
return keys
. take ( pattern . length - 1 )
. every ( ( k , i ) => k === pattern [ i + 1 ] ) ;
}
} )
. filter ( matched => matched )
. subscribe ( ( ) => console . log ( 'Secret code matched!' ) ) ; const pattern = [ ... ] ;
// Imperative
document . addEventListener ( 'keydown' , e => {
const key = e . key ;
if ( key === pattern [ 0 ] ) {
let i = 1 ;
const handler = ( e ) => {
const nextKey = e . key ;
if ( nextKey !== pattern [ i ++ ] ) {
document . removeEventListener ( 'keydown' , handler )
} else if ( pattern . length === i ) {
console . log ( 'Secret code matched!' ) ;
document . removeEventListener ( 'keydown' , handler )
}
} ;
document . addEventListener ( 'keydown' , handler ) ;
}
} , { once : true } ) ;Observable Observables เป็นวัตถุชั้นหนึ่งที่เป็นตัวแทนของเหตุการณ์ที่สามารถทำซ้ำได้ พวกเขาเป็นเหมือนสัญญา แต่สำหรับหลาย ๆ เหตุการณ์และโดยเฉพาะอย่างยิ่งกับการรวม EventTarget พวกเขาเป็นเหตุการณ์ที่สัญญาคือการโทรกลับ พวกเขาสามารถ:
subscribe()Observable.map() ที่จะแต่งและเปลี่ยนโดยไม่มีเว็บของการโทรกลับแบบซ้อนกัน ยังดีกว่าการเปลี่ยนจากตัวจัดการเหตุการณ์➡สังเกตได้ง่ายกว่าการเรียกกลับ➡สัญญาเนื่องจากผู้สังเกตการณ์รวมเข้าด้วยกันอย่างดีบน EventTarget ซึ่งเป็นวิธีการสมัครสมาชิกจากแพลตฟอร์มและสคริปต์ที่กำหนดเอง เป็นผลให้นักพัฒนาสามารถใช้สิ่งที่สังเกตได้โดยไม่ต้องย้ายรหัสจำนวนมากบนแพลตฟอร์มเนื่องจากมันเป็นเรื่องง่ายที่คุณจะจัดการกับเหตุการณ์ในวันนี้
รูปร่าง API ที่เสนอสามารถพบได้ใน https://wicg.github.io/observable/#core-infastrature
ผู้สร้างการผ่านที่สังเกตได้ในการโทรกลับที่ได้รับการเรียกร้องแบบซิงโครนัสเมื่อใดก็ตามที่ subscribe() เรียกว่า วิธี subscribe() สามารถเรียกได้ว่า มีจำนวนครั้งใด ๆ และการเรียกกลับมาตั้งค่าการตั้งค่า "การสมัครสมาชิก" ใหม่โดยการลงทะเบียนผู้โทร subscribe() เป็นผู้สังเกตการณ์ ด้วยสิ่งนี้ในสถานที่ที่สังเกตได้สามารถส่งสัญญาณจำนวนเหตุการณ์ใด ๆ ไปยังผู้สังเกตการณ์ผ่านการโทรกลับ next() ตามด้วยการโทรครั้งเดียวเพื่อให้ complete() หรือ error() ส่งสัญญาณว่ากระแสข้อมูลเสร็จสิ้น
const observable = new Observable ( ( subscriber ) => {
let i = 0 ;
setInterval ( ( ) => {
if ( i >= 10 ) subscriber . complete ( ) ;
else subscriber . next ( i ++ ) ;
} , 2000 ) ;
} ) ;
observable . subscribe ( {
// Print each value the Observable produces.
next : console . log ,
} ) ; ในขณะที่การสังเกตที่กำหนดเองสามารถเป็นประโยชน์ได้ด้วยตัวเองกรณีการใช้งานหลักที่พวกเขาปลดล็อคอยู่กับการจัดการเหตุการณ์ การสังเกตการณ์ที่ส่งคืนโดย EventTarget#when() วิธีการถูกสร้างขึ้นโดยการโทรกลับภายในที่ใช้กลไกพื้นฐานเดียวกันกับ addEventListener() ดังนั้นการเรียกสมัคร subscribe() ลงทะเบียนผู้ฟังเหตุการณ์ใหม่ซึ่งมีเหตุการณ์ที่เกิดขึ้นผ่านฟังก์ชั่นผู้สังเกตการณ์ผู้ปฏิบัติงานและสามารถคอมโพสิตกับ combinators ต่างๆที่มีให้สำหรับผู้สังเกตการณ์ทั้งหมด
Observables สามารถสร้างขึ้นได้โดยตัวสร้างพื้นเมืองของพวกเขาดังที่แสดงไว้ข้างต้นหรือโดยวิธี Observable.from() วิธีการคงที่ วิธีนี้สร้างพื้นเมืองที่สังเกตได้จากวัตถุที่มีสิ่งใดต่อไปนี้ ตามลำดับนี้ :
Observable (ในกรณีนี้มันเพิ่งส่งคืนวัตถุที่กำหนด)AsyncIterable (อะไรก็ได้ที่มี Symbol.asyncIterator )Iterable (อะไรก็ได้ที่มี Symbol.iterator )Promise (หรือใด ๆ ที่สามารถทำได้) นอกจากนี้วิธีการใด ๆ บนแพลตฟอร์มที่ต้องการยอมรับสิ่งที่สังเกตได้ว่าเป็นอาร์กิวเมนต์เว็บ IDL หรือส่งคืนจากการโทรกลับซึ่งประเภทการส่งคืนสามารถ Observable สามารถทำได้ด้วยวัตถุใด ๆ ข้างต้นเช่นกัน เราสามารถทำสิ่งนี้ให้สำเร็จได้ในหนึ่งในสองวิธีที่เราจะสรุปในข้อกำหนดที่สังเกตได้:
Observable ซึ่งดำเนินการกับวัตถุ ECMASCRIPT นี้➡การแปลงเว็บ IDL โดยอัตโนมัติเช่น Web IDL ทำสำหรับประเภทอื่น ๆany และมีร้อยแก้วสเป็คที่สอดคล้องกันเรียกใช้อัลกอริทึมการแปลงทันทีที่ข้อกำหนดที่สังเกตได้จะจัดหา สิ่งนี้คล้ายกับสิ่งที่ Streams Standard ทำกับ Async Iterables ในปัจจุบันบทสนทนาใน #60 โน้มตัวไปยังตัวเลือก (1)
สิ่งสำคัญที่สังเกตได้คือ "ขี้เกียจ" ในการที่พวกเขาไม่ได้เริ่มปล่อยข้อมูลจนกว่าพวกเขาจะสมัครรับและไม่ต้องคิวข้อมูลใด ๆ ก่อน สมัครสมาชิก พวกเขายังสามารถเริ่มเปล่งข้อมูลแบบซิงโครนัสในระหว่างการสมัครสมาชิกซึ่งแตกต่างจากสัญญาที่มักจะคิว microtasks เมื่อเรียกใช้ .then() ตัวจัดการ พิจารณาตัวอย่างนี้:
el . when ( 'click' ) . subscribe ( { next : ( ) => console . log ( 'One' ) } ) ;
el . when ( 'click' ) . find ( ( ) => { … } ) . then ( ( ) => console . log ( 'Three' ) ) ;
el . click ( ) ;
console . log ( 'Two' ) ;
// Logs "One" "Two" "Three" โดยการใช้ AbortController คุณสามารถยกเลิกการสมัครจากที่สังเกตได้แม้จะปล่อยข้อมูลแบบซิงโครนัส ระหว่าง การสมัครสมาชิก:
// An observable that synchronously emits unlimited data during subscription.
let observable = new Observable ( ( subscriber ) => {
let i = 0 ;
while ( true ) {
subscriber . next ( i ++ ) ;
}
} ) ;
let controller = new AbortController ( ) ;
observable . subscribe ( {
next : ( data ) => {
if ( data > 100 ) controller . abort ( ) ;
} } , { signal : controller . signal } ,
} ) ; มันเป็นสิ่งสำคัญสำหรับสมาชิกที่สังเกตได้ที่จะสามารถลงทะเบียนการโทรกลับโดยพลการเพื่อทำความสะอาดทรัพยากรใด ๆ ที่เกี่ยวข้องกับการสมัครสมาชิก การฉีกขาดสามารถลงทะเบียนได้จากภายในการเรียกกลับการสมัครสมาชิกส่งผ่านไปยังตัวสร้าง Observable เมื่อเรียกใช้ (เมื่อสมัครรับสมัคร) การสมัครสมาชิกสามารถลงทะเบียนฟังก์ชั่นการฉีกขาดผ่าน subscriber.addTeardown()
หากผู้สมัครสมาชิกถูกยกเลิกไปแล้ว (เช่น subscriber.signal.aborted เป็น true ) การเรียกกลับการฉีกขาดที่กำหนดจะถูกเรียกใช้ทันทีจากภายใน addTeardown() มิฉะนั้นจะถูกเรียกใช้ร่วมกัน:
complete() หลังจากผู้ใช้งานที่สมบูรณ์ (ถ้ามี) ถูกเรียกใช้error() หลังจากตัวจัดการข้อผิดพลาดของสมาชิก (ถ้ามี) ถูกเรียกใช้ เราเสนอตัวดำเนินการต่อไปนี้นอกเหนือจากอินเทอร์เฟซ Observable :
catch()Promise#catch() จะต้องใช้การโทรกลับซึ่งถูกไล่ออกหลังจากเกิดข้อผิดพลาดที่สังเกตได้ จากนั้นจะแมปไปยังสิ่งที่สังเกตได้ใหม่ส่งคืนโดยการโทรกลับเว้นแต่ข้อผิดพลาดจะกลับมาใหม่takeUntil(Observable)finally()Promise.finally() จะต้องใช้การโทรกลับซึ่งจะถูกไล่ออกหลังจากที่การสังเกตเสร็จสมบูรณ์ในทางใดทางหนึ่ง ( complete() / error() )Observable ซึ่งสะท้อนแหล่งที่สังเกตได้อย่างแน่นอน การโทรกลับผ่านไป finally จะถูกไล่ออกเมื่อการสมัครสมาชิกไปยังสิ่งที่สังเกตได้นั้นถูกยกเลิกไม่ว่าด้วย เหตุผลใดก็ตาม ทันทีหลังจากที่แหล่งข้อมูลเสร็จสิ้นหรือข้อผิดพลาดหรือเมื่อผู้บริโภคยกเลิกการสมัครสมาชิกโดยการยกเลิกการสมัครสมาชิก เวอร์ชันข้างต้นมักจะมีอยู่ในการใช้งาน Userland ของ Observables เนื่องจากมีประโยชน์สำหรับเหตุผลเฉพาะที่สังเกตได้ แต่นอกเหนือจากสิ่งเหล่านี้เรายังเสนอชุดของผู้ให้บริการทั่วไปที่เป็นไปตามแพลตฟอร์มที่มีอยู่ก่อนหน้านี้และสามารถเพิ่มยูทิลิตี้และการยอมรับได้อย่างมาก สิ่งเหล่านี้มีอยู่ใน iterables อื่น ๆ และได้มาจากข้อเสนอผู้ช่วยตัววนซ้ำของ TC39 ซึ่งเพิ่มวิธีการต่อไปนี้ให้กับ Iterator.prototype :
map()filter()take()drop()flatMap()reduce()toArray()forEach()some()every()find() และวิธีการต่อไปนี้แบบคงที่บนตัวสร้าง Iterator ซ้ำ:
from() เราคาดว่าห้องสมุด Userland จะจัดหาผู้ให้บริการเฉพาะกลุ่มที่รวมเข้ากับ API Observable จากข้อเสนอนี้ซึ่งอาจจัดส่งได้หากพวกเขาได้รับแรงผลักดันเพียงพอที่จะจบการศึกษาไปยังแพลตฟอร์ม แต่สำหรับข้อเสนอเริ่มต้นนี้เราต้องการ จำกัด ชุดของตัวดำเนินการให้กับผู้ที่ทำตามก่อนหน้าดังกล่าวข้างต้นคล้ายกับวิธีการที่ API ของเว็บแพลตฟอร์มที่ประกาศ Setlike และ Maplelike มีคุณสมบัติดั้งเดิมที่ได้รับแรงบันดาลใจจากแผนที่ของ TC39 และวัตถุที่ตั้งไว้ ดังนั้นเราจึงพิจารณาการอภิปรายส่วนใหญ่เกี่ยวกับการขยายชุดนี้เป็นขอบเขตสำหรับข้อเสนอ เริ่มต้น ซึ่งเหมาะสำหรับการอภิปรายในภาคผนวก ผู้ให้บริการยาวใด ๆ ที่สามารถติดตาม ได้ หากมีการสนับสนุน API ที่สังเกตได้ง่ายที่นำเสนอในผู้อธิบายนี้
โปรดทราบว่าผู้ประกอบการ every() , find() , some() , และ reduce() การส่งคืนสัญญาที่มีการกำหนดเวลาแตกต่างจากที่สังเกตได้ซึ่งบางครั้งหมายถึงตัวจัดการเหตุการณ์ที่โทร e.preventDefault() จะช้าเกินไป ดูส่วนข้อกังวลซึ่งมีรายละเอียดเพิ่มเติม
เพื่อแสดงให้เห็นว่าสิ่งที่สังเกตได้พอดีกับภูมิทัศน์ปัจจุบันของดั้งเดิมปฏิกิริยาอื่น ๆ ดูตารางด้านล่างซึ่งเป็นความพยายามในการรวมสองตารางอื่น ๆ ที่จัดประเภทดั้งเดิมปฏิกิริยาโดยการมีปฏิสัมพันธ์กับผู้ผลิตและผู้บริโภค:
| เอกพจน์ | พหูพจน์ | |||
|---|---|---|---|---|
| เชิงพื้นที่ | เกี่ยวกับชั่วคราว | เชิงพื้นที่ | เกี่ยวกับชั่วคราว | |
| ดัน | ค่า | สัญญา | สังเกตได้ | |
| ดึง | การทำงาน | async iterator | ทำซ้ำได้ | async iterator |
ผู้สังเกตการณ์ถูกเสนอเป็นครั้งแรกในแพลตฟอร์มใน TC39 ในเดือนพฤษภาคมปี 2558 ข้อเสนอไม่สามารถได้รับแรงฉุดส่วนหนึ่งเป็นเพราะการคัดค้านบางอย่างว่า API นั้นเหมาะสมที่จะเป็นแบบดั้งเดิมระดับภาษา ในความพยายามที่จะต่ออายุข้อเสนอในระดับที่สูงขึ้นของสิ่งที่เป็นนามธรรมปัญหา whatwg dom ถูกยื่นในเดือนธันวาคมปี 2560 แม้จะมีความต้องการของนักพัฒนาที่เพียงพอการอภิปราย จำนวนมาก และไม่มีผู้คัดค้านที่แข็งแกร่งข้อเสนอ DOM ที่สังเกตได้ส่วนใหญ่ยังคงเป็นเวลาหลายปี
ต่อมาในปี 2562 ความพยายามในการฟื้นฟูข้อเสนอนั้นกลับมาที่ที่เก็บ TC39 ดั้งเดิมซึ่งเกี่ยวข้องกับการทำให้ง่ายขึ้นของ API และเพิ่มการสนับสนุนสำหรับปัญหา "Firehose" แบบซิงโครนัส
ที่เก็บนี้เป็นความพยายามที่จะหายใจชีวิตอีกครั้งในข้อเสนอที่สังเกตได้ด้วยความหวังว่าจะจัดส่งเวอร์ชันของมันไปยังแพลตฟอร์มเว็บ
ในการอภิปรายก่อนหน้านี้ Ben Lesh ได้แสดงการใช้งานผู้ใช้ที่กำหนดเองหลายรายการของ Primitives ที่สังเกตได้ซึ่ง RXJS เป็นที่นิยมมากที่สุดใน "การดาวน์โหลด 47,000,000+ ต่อสัปดาห์ "
start และ unsubscribe สำหรับการสังเกตและรับ Subscription ก่อนผลตอบแทนActor ของพวกเขาเพื่ออนุญาตให้สมัครสมาชิกการเปลี่ยนแปลงในสถานะดังที่แสดงในเบ็ด useActor ของพวกเขา การใช้สิ่งที่สังเกตได้เหมือนกันนั้นเป็นส่วนที่บันทึกไว้ของการเปลี่ยนแปลงของเครื่อง Access State เมื่อใช้ XSTATE กับ SOLIDJS{ subscribe(callback: (value: T) => void): () => void } ในรหัสเราเตอร์และ deverreddata สิ่งนี้ชี้ให้เห็นว่าผู้ดูแลเป็นแรงบันดาลใจจากการสังเกต{ subscribe(callback: (value: T) => void): () => void } สำหรับสัญญาณของพวกเขา{ subscribe(callback: (value: T) => void): () => void } ในหลายสถานที่{ subscribe(callback: (value: T) => void): () => void }{ observe_(callback: (value: T)): () => void }| async ฟังก์ชั่น | async "Async Pipe" ในเทมเพลตเมื่อพิจารณาถึงศิลปะก่อนหน้านี้ในพื้นที่นี้มี "สัญญาที่สังเกตได้" สาธารณะ
นอกจากนี้ JavaScript APIs จำนวนมากพยายามที่จะปฏิบัติตามสัญญาที่กำหนดโดยข้อเสนอ TC39 ตั้งแต่ปี 2558 จนถึงตอนท้ายมีห้องสมุดสัญลักษณ์ Symbol.observable ที่สังเกตได้ซึ่งเป็นสัญลักษณ์ของ Ponyfills (polyfills) symbol-observable มีแพ็คเกจขึ้นอยู่กับ NPM 479 ชุดและดาวน์โหลดมากกว่า 13,000,000 ครั้งต่อสัปดาห์ ซึ่งหมายความว่ามีอย่างน้อย 479 แพ็คเกจสำหรับ NPM ที่ใช้สัญญาที่สังเกตได้ในบางวิธี
สิ่งนี้คล้ายกับวิธีการที่สัญญา/A+ สเปคที่พัฒนาขึ้นก่อนที่ Promise S จะถูกนำมาใช้ใน ES2015 เป็นภาษาดั้งเดิมชั้นหนึ่ง
หนึ่งในข้อกังวลหลักที่แสดงในเธรด whatwg dom ดั้งเดิมเกี่ยวข้องกับ APIs ที่ให้สัญญากับที่สังเกตได้เช่นข้อเสนอ first() footgun ที่มีศักยภาพที่นี่ด้วยการกำหนดเวลา Microtask และการรวมเหตุการณ์ โดยเฉพาะรหัสที่ดูไร้เดียงสาต่อไปนี้จะไม่ทำงาน เสมอไป :
element
. when ( 'click' )
. first ( )
. then ( ( e ) => {
e . preventDefault ( ) ;
// Do something custom...
} ) ; หาก Observable#first() ส่งคืนสัญญาที่แก้ไขเมื่อเหตุการณ์แรกถูกยิงใน EventTarget ดังนั้นสัญญาที่ผู้ใช้จัดสรรให้ผู้ใช้จากนั้นตัวจัดการ .then() จะเรียกใช้:
element.click() )e.preventDefault() จะเกิดขึ้นสายเกินไปและถูกละเว้นอย่างมีประสิทธิภาพใน WebIDL หลังจากการเรียกกลับถูกเรียกใช้อัลกอริทึม HTML ทำความสะอาดหลังจากเรียกใช้สคริปต์ และอัลกอริทึมนี้เรียกว่า จะทำการตรวจสอบ MicroTask Checkpoint ถ้าและเฉพาะในกรณีที่สแต็ก JavaScript ว่างเปล่า
อย่างเป็นรูปธรรมนั่นหมายถึง element.click() ในตัวอย่างข้างต้นขั้นตอนต่อไปนี้เกิดขึ้น:
element.click() บริบทการดำเนินการ JavaScript จะถูกผลักลงบนสแต็กก่อนclick ภายใน (อันที่สร้างขึ้นโดย Observable#from() การใช้งาน) บริบทการดำเนินการ JavaScript อื่น จะถูกผลักลงบนสแต็กขณะที่ Webidl เตรียมที่จะเรียกใช้การโทรกลับภายในObservable#first() ; ตอนนี้คิว microtask มีตัวจัดการที่ผู้ใช้จัดหาให้กับสัญญา then() ตัวจัดการซึ่งจะยกเลิกเหตุการณ์เมื่อมันทำงานclick ภายในเส้นทางที่เหลือของเส้นทางเหตุการณ์จะดำเนินต่อไปเนื่องจากเหตุการณ์ไม่ได้ถูกยกเลิกในระหว่างหรือทันทีหลังจากการโทรกลับ เหตุการณ์ทำทุกอย่างที่ปกติจะทำ (ส่งแบบฟอร์ม alert() ผู้ใช้ ฯลฯ )element.click() เสร็จสิ้นและบริบทการดำเนินการขั้นสุดท้ายจะถูกนำมาจากสแต็กและคิว Microtask จะถูกล้างออก ตัวจัดการที่ใช้งานที่ผู้ใช้ใช้ .then() เรียกใช้ซึ่งพยายามยกเลิกเหตุการณ์ที่สายเกินไป สองสิ่งลดความกังวลนี้ ก่อนอื่นมีวิธีแก้ปัญหาที่ง่ายมากที่จะหลีก เลี่ยง กรณีที่ e.preventDefault() ของคุณอาจทำงานสายเกินไป:
element
. when ( 'click' )
. map ( ( e ) => ( e . preventDefault ( ) , e ) )
. first ( ) ; ... หรือถ้าสังเกตได้มีวิธี .do() (ดู whatwg/dom#544 (ความคิดเห็น)):
element
. when ( 'click' )
. do ( ( e ) => e . preventDefault ( ) )
. first ( ) ; ... หรือโดยการปรับเปลี่ยนความหมายของ first() เพื่อเรียกโทรกลับซึ่งสร้างค่าที่สัญญาที่ส่งคืนจะแก้ไขได้เป็น:
el . when ( 'submit' )
. first ( ( e ) => e . preventDefault ( ) )
. then ( doMoreStuff ) ;ประการที่สอง "Quirk" นี้มีอยู่แล้วในระบบนิเวศที่สังเกตได้ในปัจจุบันและไม่มีข้อกังวลหรือรายงานจากชุมชนที่นักพัฒนากำลังวิ่งเข้ามาในเรื่องนี้อย่างต่อเนื่อง สิ่งนี้ให้ความมั่นใจว่าการอบพฤติกรรมนี้ลงในแพลตฟอร์มเว็บจะไม่เป็นอันตราย
มีการอภิปรายมากมายเกี่ยวกับสถานที่มาตรฐานใดที่ควรเป็นเจ้าภาพข้อเสนอที่สังเกตได้ สถานที่นั้นไม่สำคัญเพราะมันตัดสินใจได้อย่างมีประสิทธิภาพว่าสิ่งที่สังเกตได้กลายเป็นระดับภาษาแบบดั้งเดิมเช่น Promise S การจัดส่งในเครื่องยนต์เบราว์เซอร์ JavaScript ทั้งหมดหรือแพลตฟอร์มเว็บดั้งเดิมที่มีการพิจารณา (แต่ เป็นทางเลือก ทางเทคนิค) ในสภาพแวดล้อมอื่น ๆ เช่น node.js (ดู AbortController )
Observables มีจุดมุ่งหมายรวมเข้ากับอินเทอร์เฟซการปล่อยกิจกรรมหลัก ( EventTarget ) และการยกเลิกแบบดั้งเดิม ( AbortController ) ที่อาศัยอยู่ในแพลตฟอร์มเว็บ ตามที่เสนอที่นี่ผู้สังเกตการณ์เข้าร่วมส่วนประกอบที่เชื่อมต่ออย่างยิ่งที่มีอยู่นี้จากมาตรฐาน DOM: Observables ขึ้นอยู่กับ AbortController/Abortsignal ซึ่งขึ้นอยู่กับ EventTarget และ EventTarget ขึ้นอยู่กับทั้ง Observables และ AbortController/Abortsignal เนื่องจากเรารู้สึกว่าสิ่งที่สังเกตได้เหมาะสมที่สุดที่ซึ่งการสนับสนุนดั้งเดิมอาศัยอยู่สถานที่มาตรฐาน whatwg น่าจะเป็นสถานที่ที่ดีที่สุดในการพัฒนาข้อเสนอนี้ นอกจากนี้ ECMASCRIPT ที่ไม่ใช่ WEB ECMASCRIPT Embedders เช่น Node.js และ Deno จะยังคงสามารถนำไปใช้ที่สังเกตได้และมีแนวโน้มที่จะได้รับความมุ่งมั่นในการยกเลิกแพลตฟอร์มเว็บและเหตุการณ์
สิ่งนี้ไม่ได้ขัดขวางการกำหนดมาตรฐานในอนาคตของการปล่อยเหตุการณ์และการยกเลิกครั้งแรกใน TC39 ในอนาคตสิ่งที่สังเกตได้ในทางทฤษฎีอาจถูกเลเยอร์ในภายหลัง แต่สำหรับตอนนี้เรามีแรงจูงใจที่จะก้าวหน้าใน whatwg
ในความพยายามที่จะหลีกเลี่ยงการปรับเปลี่ยนการสนทนานี้เราขอเรียกร้องให้ผู้อ่านดูความคิดเห็นการสนทนาต่อไปนี้:
ส่วนนี้ปิดการรวบรวมมาตรฐานเว็บและมาตรฐานตำแหน่งที่ใช้ในการติดตามชีวิตของข้อเสนอที่สังเกตได้นอกที่เก็บนี้
Observables ได้รับการออกแบบมาเพื่อให้การจัดการเหตุการณ์การยศาสตร์และดีขึ้นมากขึ้น ดังนั้นผลกระทบของพวกเขาที่มีต่อผู้ใช้จะเป็นทางอ้อมส่วนใหญ่มาในรูปแบบของผู้ใช้ที่ต้องดาวน์โหลด JavaScript น้อยลงเพื่อใช้รูปแบบที่นักพัฒนาใช้ในปัจจุบันใช้ไลบรารีบุคคลที่สามสำหรับ ตามที่ระบุไว้ข้างต้นในผู้อธิบายมีระบบนิเวศที่สังเกตได้จากผู้ใช้ที่เจริญรุ่งเรืองซึ่งส่งผลให้มีการดาวน์โหลดไบต์มากเกินไปทุกวัน
ในความพยายามที่จะประมวลผลแบบดั้งเดิมของผู้ใช้ที่แข็งแกร่งของ API ที่สังเกตได้ข้อเสนอนี้จะประหยัดการใช้งานที่กำหนดเองหลายสิบครั้งจากการดาวน์โหลดทุกวัน
นอกจากนี้ในฐานะ API เช่น EventTarget , AbortController และอีกหนึ่งที่เกี่ยวข้องกับ Promise S ช่วยให้นักพัฒนาสามารถสร้างกระแสการจัดการเหตุการณ์ที่ซับซ้อนน้อยลงโดยการสร้างพวกเขาอย่างประกาศซึ่งอาจทำให้พวกเขาสามารถสร้างประสบการณ์ผู้ใช้ที่ดีบนเว็บ