无法使函数 return 值等待获取的结果 - 现在是多线程的
Can't make function return value wait for fetched result - Now multithreaded
以前我在单线程情况下问过这个问题,得到的答案适用于那种情况 。但是现在我试图在多线程环境中回答同样的问题。
我正在使用 OpenLayers 绘制 UK Ordnance Survey 地图。我有工作示例,但他们使用不太安全的方法通过在 URL 中提供密钥来访问 UKOS 服务器,而我想改用更安全的 OAuth2 安排。为此,我已经有一个 CGI 脚本查询服务器以获取会话令牌,并将其 return 设置为 JSON。在单线程环境下,让地图绘制页面等待其return即可解决问题,如下:
UKOS.getToken = async function () {
if (
!UKOS.token ||
new Date().getTime() >
UKOS.token.issued_at + 1000 * (UKOS.token.expires_in - 15)
)
UKOS.token = await fetch(/* CGI Script */).then((response) =>
response.json()
);
console.log(UKOS.token.access_token);
return UKOS.token.access_token;
};
// ...
UKOS.getToken()
.then(token => fetch(UKOS.WMTS + "&request=GetCapabilities&version=2.0.0", { headers: {Authorization: "Bearer " + token} }) )
.then( /* etc */ )
但是,现在我使用的是多线程环境,上面的方法不再有效,因为在第一次调用 getToken() 启动令牌获取后,后续调用在它可以 returned,并且,具体取决于我尝试编写 getToken() 代码的方式,要么在一个令牌执行时启动多个令牌获取(直到它过期),要么不要等待令牌成为 returned,并失败,因为它仍然是空的。这是我目前尝试改编的 getToken() ...
UKOS = {
token: null,
tokStat: 0,
tokProm: null
}
UKOS.getToken = async function() {
var result;
console.log( "UKOS.tokStat: " + UKOS.tokStat );
switch( UKOS.tokStat ) {
case 1: await UKOS.tokProm;
await UKOS.token;
// Deliberate fall through
console.log( "UKOS.token: " + UKOS.token );
console.log( "Falling through from 1 to 2 ..." );
case 2: if( new Date().getTime() < UKOS.token.issued_at + 1000*(UKOS.token.expires_in - 15) ) {
result = UKOS.token.access_token;
break;
}
// Deliberate fall through
console.log( "Falling through from 2 to 0 ..." );
case 0: UKOS.tokStat = 1;
UKOS.tokProm = fetch( location.protocol + '//' + location.host + "/cgi-bin/OSDataOAuth.py" );
UKOS.token = await UKOS.tokProm.then( response => response.json() );
UKOS.tokStat = 2;
result = UKOS.token.access_token;
}
return result;
};
没有两个 'await' 限定符,当然后续调用只会崩溃到 'case 2' 并失败,因为令牌仍然为空,有了它们,这就是结果 [不相关的细节已删除] ...
// Debugging console log message from routine calling getToken()
url: https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=G…TRIX=EPSG%3A27700%3A0&TILEROW=6&TILECOL=1&FORMAT=image%2Fpng
// Debugging console log message from getToken()
UKOS.tokStat: 0
url: https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=G…TRIX=EPSG%3A27700%3A0&TILEROW=5&TILECOL=1&FORMAT=image%2Fpng
UKOS.tokStat: 1
[Repeated multiple times]
url: undefined
url: https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=G…TRIX=EPSG%3A27700%3A0&TILEROW=6&TILECOL=1&FORMAT=image%2Fpng
[Repeated multiple times]
url: https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=G…TRIX=EPSG%3A27700%3A0&TILEROW=6&TILECOL=2&FORMAT=image%2Fpng
UKOS.tokStat: 1
// Token fetched successfully
// (see end, the only map tile fetch that succeeds is almost certainly the first to call getToken())
XHRGET http://local.localhost/cgi-bin/OSDataOAuth.py
[HTTP/1.1 200 OK 1590ms]
url: https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=G…TRIX=EPSG%3A27700%3A0&TILEROW=5&TILECOL=2&FORMAT=image%2Fpng
UKOS.tokStat: 1
[Repeated multiple times]
UKOS.token: null
Falling through from 1 to 2 ...
[Repeated multiple times]
Falling through from 1 to 2 ...
Uncaught (in promise) TypeError: UKOS.token is null
getToken http://local.localhost/JavaJive/ProgScriptWeb/UKOSOpenLayers2.shtml:206
setImgSrc http://local.localhost/JavaJive/ProgScriptWeb/UKOSOpenLayers2.shtml:301
initImage https://openlayers.org/api/2.13/OpenLayers.debug.js:29733
renderTile https://openlayers.org/api/2.13/OpenLayers.debug.js:29608
draw https://openlayers.org/api/2.13/OpenLayers.debug.js:29581
initGriddedTiles https://openlayers.org/api/2.13/OpenLayers.debug.js:30951
moveTo https://openlayers.org/api/2.13/OpenLayers.debug.js:30422
moveTo https://openlayers.org/api/2.13/OpenLayers.debug.js:83261
moveTo https://openlayers.org/api/2.13/OpenLayers.debug.js:9273
setCenter https://openlayers.org/api/2.13/OpenLayers.debug.js:9035
setBaseLayer http://local.localhost/JavaJive/ProgScriptWeb/UKOSOpenLayers2.shtml:442
addLayer https://openlayers.org/api/2.13/OpenLayers.debug.js:8354
initOSMap http://local.localhost/JavaJive/ProgScriptWeb/UKOSOpenLayers2.shtml:457
onload http://local.localhost/JavaJive/ProgScriptWeb/UKOSOpenLayers2.shtml:1
UKOSOpenLayers2.shtml:206:34
[Repeated multiple times]
// I think this one, the only one to successfully fetch a map tile is probably the first attempt to do so, which correctly waited for the returned token
XHRGET https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=Leisure_27700&STYLE=default&TILEMATRIXSET=EPSG:27700&TILEMATRIX=EPSG:27700:0&TILEROW=6&TILECOL=1&FORMAT=image/png
[HTTP/1.1 200 OK 1417ms]
dataUrl: blob:http://local.localhost/2f7fa455-3385-4d10-ab0e-b70999658137
以上内容的有趣之处在于,这两个 'await' 似乎确实有所成就,但还不够,因为没有他们各自...
UKOS.tokStat: 1
... 紧接着是 ...
UKOS.token: null
Falling through from 1 to 2 ...
...但他们显然没有等待令牌被 returned,事实证明他们在第一次调用最终成功之前都失败了。谁能建议如何让第二次和后续调用等待第一次调用请求的令牌?
在您的原始代码中,函数有两条路径。
- 您已经有令牌
- return它
- 您还没有令牌
- 发出异步请求以获取一个
- 当请求解析时存储令牌
- return它
当您快速连续调用该函数两次时,您会掉入 2.1 和 2.2 之间的差距。
停止存储令牌。将 promise return 存储为 2.1。
这样,如果请求是 in-flight,您可以重复使用它。
UKOS.getToken = async function () {
if (UKOS.tokenPromise) {
const token = await UKOS.tokenPromise;
const now = new Date().getTime();
if (now <= token.issued_at + 1000 * (token.expires_in - 15)) {
// Token still good
return token.access_token;
}
// If we get here, the token has expired
}
// If we get here, the token has expired or we didn't have a promise in the first place
// Create or update token promise
UKOS.tokenPromise = fetch(/* CGI Script */).then((response) => response.json());
// Wait for it to resolve
const token = UKOS.tokenPromise;
// Assume the new token is good
return UKOS.token.access_token;
};
这是我的原始代码的工作版本,根据 Quentin 的方法进行了修改,虽然它比我最初想象的要远一些,但我仍然非常感激,因为它教会了我如何处理问题:
UKOS.getToken = async function() {
var result;
console.log( "UKOS.tokStat: " + UKOS.tokStat );
do
switch( UKOS.tokStat ) {
case 1: UKOS.token = await UKOS.tokProm;
UKOS.tokStat = 2;
// Deliberate fall through
console.log( "UKOS.token: " + UKOS.token );
console.log( "Falling through from 1 to 2 ..." );
case 2: if( new Date().getTime() < UKOS.token.issued_at + 1000*(UKOS.token.expires_in - 15) )
{
result = UKOS.token.access_token;
break;
}
// Deliberate fall through
console.log( "Falling through from 2 to 0 ..." );
case 0: UKOS.tokProm = null;
UKOS.token = null;
UKOS.tokStat = 1;
// First call arrives here, but without 'await' in the
// the statement below it fails by returning null while
// later calls work. However, if we put 'await' here,
// then the first call succeeds while later calls find
// UKOS.tokProm = null and so fail. Therefore somehow
// we must process first and later calls the same, and
// this happens by the 'while' statement, which forces
// the first call back through the 'switch' statement.
UKOS.tokProm = fetch( location.protocol + '//' + location.host + "/cgi-bin/OSDataOAuth.py" )
.then( response => response.json() );
}
while( !UKOS.token )
return result;
};
以前我在单线程情况下问过这个问题,得到的答案适用于那种情况
我正在使用 OpenLayers 绘制 UK Ordnance Survey 地图。我有工作示例,但他们使用不太安全的方法通过在 URL 中提供密钥来访问 UKOS 服务器,而我想改用更安全的 OAuth2 安排。为此,我已经有一个 CGI 脚本查询服务器以获取会话令牌,并将其 return 设置为 JSON。在单线程环境下,让地图绘制页面等待其return即可解决问题,如下:
UKOS.getToken = async function () {
if (
!UKOS.token ||
new Date().getTime() >
UKOS.token.issued_at + 1000 * (UKOS.token.expires_in - 15)
)
UKOS.token = await fetch(/* CGI Script */).then((response) =>
response.json()
);
console.log(UKOS.token.access_token);
return UKOS.token.access_token;
};
// ...
UKOS.getToken()
.then(token => fetch(UKOS.WMTS + "&request=GetCapabilities&version=2.0.0", { headers: {Authorization: "Bearer " + token} }) )
.then( /* etc */ )
但是,现在我使用的是多线程环境,上面的方法不再有效,因为在第一次调用 getToken() 启动令牌获取后,后续调用在它可以 returned,并且,具体取决于我尝试编写 getToken() 代码的方式,要么在一个令牌执行时启动多个令牌获取(直到它过期),要么不要等待令牌成为 returned,并失败,因为它仍然是空的。这是我目前尝试改编的 getToken() ...
UKOS = {
token: null,
tokStat: 0,
tokProm: null
}
UKOS.getToken = async function() {
var result;
console.log( "UKOS.tokStat: " + UKOS.tokStat );
switch( UKOS.tokStat ) {
case 1: await UKOS.tokProm;
await UKOS.token;
// Deliberate fall through
console.log( "UKOS.token: " + UKOS.token );
console.log( "Falling through from 1 to 2 ..." );
case 2: if( new Date().getTime() < UKOS.token.issued_at + 1000*(UKOS.token.expires_in - 15) ) {
result = UKOS.token.access_token;
break;
}
// Deliberate fall through
console.log( "Falling through from 2 to 0 ..." );
case 0: UKOS.tokStat = 1;
UKOS.tokProm = fetch( location.protocol + '//' + location.host + "/cgi-bin/OSDataOAuth.py" );
UKOS.token = await UKOS.tokProm.then( response => response.json() );
UKOS.tokStat = 2;
result = UKOS.token.access_token;
}
return result;
};
没有两个 'await' 限定符,当然后续调用只会崩溃到 'case 2' 并失败,因为令牌仍然为空,有了它们,这就是结果 [不相关的细节已删除] ...
// Debugging console log message from routine calling getToken()
url: https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=G…TRIX=EPSG%3A27700%3A0&TILEROW=6&TILECOL=1&FORMAT=image%2Fpng
// Debugging console log message from getToken()
UKOS.tokStat: 0
url: https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=G…TRIX=EPSG%3A27700%3A0&TILEROW=5&TILECOL=1&FORMAT=image%2Fpng
UKOS.tokStat: 1
[Repeated multiple times]
url: undefined
url: https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=G…TRIX=EPSG%3A27700%3A0&TILEROW=6&TILECOL=1&FORMAT=image%2Fpng
[Repeated multiple times]
url: https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=G…TRIX=EPSG%3A27700%3A0&TILEROW=6&TILECOL=2&FORMAT=image%2Fpng
UKOS.tokStat: 1
// Token fetched successfully
// (see end, the only map tile fetch that succeeds is almost certainly the first to call getToken())
XHRGET http://local.localhost/cgi-bin/OSDataOAuth.py
[HTTP/1.1 200 OK 1590ms]
url: https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=G…TRIX=EPSG%3A27700%3A0&TILEROW=5&TILECOL=2&FORMAT=image%2Fpng
UKOS.tokStat: 1
[Repeated multiple times]
UKOS.token: null
Falling through from 1 to 2 ...
[Repeated multiple times]
Falling through from 1 to 2 ...
Uncaught (in promise) TypeError: UKOS.token is null
getToken http://local.localhost/JavaJive/ProgScriptWeb/UKOSOpenLayers2.shtml:206
setImgSrc http://local.localhost/JavaJive/ProgScriptWeb/UKOSOpenLayers2.shtml:301
initImage https://openlayers.org/api/2.13/OpenLayers.debug.js:29733
renderTile https://openlayers.org/api/2.13/OpenLayers.debug.js:29608
draw https://openlayers.org/api/2.13/OpenLayers.debug.js:29581
initGriddedTiles https://openlayers.org/api/2.13/OpenLayers.debug.js:30951
moveTo https://openlayers.org/api/2.13/OpenLayers.debug.js:30422
moveTo https://openlayers.org/api/2.13/OpenLayers.debug.js:83261
moveTo https://openlayers.org/api/2.13/OpenLayers.debug.js:9273
setCenter https://openlayers.org/api/2.13/OpenLayers.debug.js:9035
setBaseLayer http://local.localhost/JavaJive/ProgScriptWeb/UKOSOpenLayers2.shtml:442
addLayer https://openlayers.org/api/2.13/OpenLayers.debug.js:8354
initOSMap http://local.localhost/JavaJive/ProgScriptWeb/UKOSOpenLayers2.shtml:457
onload http://local.localhost/JavaJive/ProgScriptWeb/UKOSOpenLayers2.shtml:1
UKOSOpenLayers2.shtml:206:34
[Repeated multiple times]
// I think this one, the only one to successfully fetch a map tile is probably the first attempt to do so, which correctly waited for the returned token
XHRGET https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=Leisure_27700&STYLE=default&TILEMATRIXSET=EPSG:27700&TILEMATRIX=EPSG:27700:0&TILEROW=6&TILECOL=1&FORMAT=image/png
[HTTP/1.1 200 OK 1417ms]
dataUrl: blob:http://local.localhost/2f7fa455-3385-4d10-ab0e-b70999658137
以上内容的有趣之处在于,这两个 'await' 似乎确实有所成就,但还不够,因为没有他们各自...
UKOS.tokStat: 1
... 紧接着是 ...
UKOS.token: null
Falling through from 1 to 2 ...
...但他们显然没有等待令牌被 returned,事实证明他们在第一次调用最终成功之前都失败了。谁能建议如何让第二次和后续调用等待第一次调用请求的令牌?
在您的原始代码中,函数有两条路径。
- 您已经有令牌
- return它
- 您还没有令牌
- 发出异步请求以获取一个
- 当请求解析时存储令牌
- return它
当您快速连续调用该函数两次时,您会掉入 2.1 和 2.2 之间的差距。
停止存储令牌。将 promise return 存储为 2.1。
这样,如果请求是 in-flight,您可以重复使用它。
UKOS.getToken = async function () {
if (UKOS.tokenPromise) {
const token = await UKOS.tokenPromise;
const now = new Date().getTime();
if (now <= token.issued_at + 1000 * (token.expires_in - 15)) {
// Token still good
return token.access_token;
}
// If we get here, the token has expired
}
// If we get here, the token has expired or we didn't have a promise in the first place
// Create or update token promise
UKOS.tokenPromise = fetch(/* CGI Script */).then((response) => response.json());
// Wait for it to resolve
const token = UKOS.tokenPromise;
// Assume the new token is good
return UKOS.token.access_token;
};
这是我的原始代码的工作版本,根据 Quentin 的方法进行了修改,虽然它比我最初想象的要远一些,但我仍然非常感激,因为它教会了我如何处理问题:
UKOS.getToken = async function() {
var result;
console.log( "UKOS.tokStat: " + UKOS.tokStat );
do
switch( UKOS.tokStat ) {
case 1: UKOS.token = await UKOS.tokProm;
UKOS.tokStat = 2;
// Deliberate fall through
console.log( "UKOS.token: " + UKOS.token );
console.log( "Falling through from 1 to 2 ..." );
case 2: if( new Date().getTime() < UKOS.token.issued_at + 1000*(UKOS.token.expires_in - 15) )
{
result = UKOS.token.access_token;
break;
}
// Deliberate fall through
console.log( "Falling through from 2 to 0 ..." );
case 0: UKOS.tokProm = null;
UKOS.token = null;
UKOS.tokStat = 1;
// First call arrives here, but without 'await' in the
// the statement below it fails by returning null while
// later calls work. However, if we put 'await' here,
// then the first call succeeds while later calls find
// UKOS.tokProm = null and so fail. Therefore somehow
// we must process first and later calls the same, and
// this happens by the 'while' statement, which forces
// the first call back through the 'switch' statement.
UKOS.tokProm = fetch( location.protocol + '//' + location.host + "/cgi-bin/OSDataOAuth.py" )
.then( response => response.json() );
}
while( !UKOS.token )
return result;
};