'callAsyncJavaScript' 可以在 Promise 示例中使用 setTimeout(..., 1000) 吗?
Is use of setTimeout(..., 1000) in Promise example possible with 'callAsyncJavaScript'?
基于WebKit的WKWebView documentation provides a callAsyncJavaScript() method有以下例子:
var p = new Promise(function (f) {
window.setTimeout("f(42)", 1000);
});
await p;
return p;
运行此示例代码模式的各种尝试return相同的“函数调用的完成处理程序不再可达”错误:
Error Domain=WKErrorDomain Code=4
"A JavaScript exception occurred"
UserInfo={
WKJavaScriptExceptionLineNumber=0,
WKJavaScriptExceptionMessage="Completion handler for function call is no longer reachable",
WKJavaScriptExceptionColumnNumber=0,
NSLocalizedDescription="A JavaScript exception occurred"
}
我已经用 callAsyncJavaScript
尝试对示例代码进行了一些调整。 callAsyncJavaScript
调用总是 return 出现相同的“...不再可达”错误。
这里有一个 运行 在浏览器 JS 控制台中没有错误的变体,它不适用于 callAsyncJavaScript
:
为什么会出错?这个例子能以某种方式成功地 运行 和 WKWebView
callAsyncJavaScript
吗?如果是,那又如何?
其他详细信息
问题可能是 Promise
在文档示例中没有正确解决。
下面的代码添加了 console.log()
跟踪信息。
let tracer = "a"
console.log("tracer:", tracer);
f = function (nProperty) {
let nResult = nProperty * 2
console.log("nResult:", nResult);
tracer = tracer + "d"
console.log("tracer:", tracer);
return nResult;
};
var p = new Promise(function (f) {
tracer = tracer + "b"
console.log("tracer:", tracer);
window.setTimeout("f(42)", 500);
});
console.log("BEFORE await:", p);
tracer = tracer + "c"
console.log("tracer:", tracer);
await p;
console.log("AFTER await:", p);
tracer = tracer + "e"
console.log("tracer:", tracer);
当在 Firefox JS 控制台中 运行 以上时,执行在 await 语句处停止。这是 Firefox JS 控制台输出:
tracer: a
tracer: ab
BEFORE await: Promise { <state>: "pending" }
tracer: abc
nResult: 84
tracer: abcd
语句 console.log("AFTER await:", p);
从未执行过。
还有一个细节...
从 Swift 程序员的角度来看,人们会期望从 Swift callAsyncJavaScript(…) 调用的成功 JavaScript 示例会 明确地 return 一个有用的结果 从 JavaScript 回到 Swift Result<Any, Error>)
.
请注意,callAsyncJavaScript(…)
作为异步 JavaScript 函数执行提供的 String
of JavaScript。
// do some JavaScript actions, await a result, and then..
return somthingUseful;
“函数调用的完成处理程序不再可达” 错误 callAsyncJavaScript() 文档示例中的问题可能是 Promise
没有 resolve
.
这是一个有效的 callAsyncJavaScript
示例(Swift 5.4、Xcode 15.5、macOS 11.4),它使用 Promise
、setTimeout
、await
并解析为 returned 数据对象。
function getPromise() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve({
'word': 'epistemology'
});
}, 2000);
});
}
function getResult() {
return getPromise()
.then(function(response) {
return response;
});
}
let data = await getResult()
.then(function(result) {
return result;
});
return data;
更新
阅读 VLAZ 提供的 了解 "f(32)"
破坏 Apple callAsyncJavaScript() 文档中提供的示例的真正原因(写在这个问题的时间)。
此外,Apple 示例在 FireFox JavaScript 控制台和 Swift callAsyncJavaScript(…)
方法,当根据 the answer provide by VLAZ.
修改示例时
从 JavaScript 到 Swift 的 return
值,如在 Swift LLDB 调试器中所见:
(lldb) po result
▿ Result<Any, Error>
- success : 42
附录:一些有用的提示
Swift callAsyncJavaScript()
方法 将提供的 JavaScript 字符串作为 WebKit 实例中的异步 JavaScript 函数 执行。因此,JavaScript 调试器和 console.log()
不适用于 Swift callAsyncJavaScript()
方法。
下面的方法可以帮助为计划的 JavaScript String
.
提供调试可见性
FireFox JavaScript 控制台可用于在 async
函数体中执行字符串:
// --- name browser window/tab ---
document.title = "Example 42";
async function callAsyncJavaScript() {
// --- begin Swift string literal section ---
let mocklog = "LOG: ";
var p = new Promise(function (f) {
mocklog += "A ";
window.setTimeout(() => f(42), 1000);
});
// p: Promise { <state>: "pending" }
mocklog += "B ";
let pResult = await p;
// p: Promise { <state>: "fulfilled", <value>: 42 }
mocklog += `C ${pResult}`;
return {"pResult": pResult, "mocklog": mocklog}
// --- end Swift string literal section ---
}
let result = await callAsyncJavaScript();
console.log("Result: ", result)
// Result: Object { pResult: 42, mocklog: "LOG: A B C 42" }
同一个JavaScript可以多行放置SwiftString
字面量:
let javaScript_42 = """
let mocklog = "LOG: ";
var p = new Promise(function (f) {
mocklog += "A ";
window.setTimeout(() => f(42), 1000);
});
// p: Promise { <state>: "pending" }
mocklog += "B ";
let pResult = await p;
// p: Promise { <state>: "fulfilled", <value>: 42 }
mocklog += `C ${pResult}`;
return {"pResult": pResult, "mocklog": mocklog}
"""
Swift 调试器结果:
(lldb) po result
▿ Result<Any, Error>
▿ success : 2 elements
▿ 0 : 2 elements
- key : pResult
- value : 42
▿ 1 : 2 elements
- key : mocklog
- value : LOG: A B C 42
脚注:添加了 mocklog
字符串作为 console.log()
的替代,因为 console.log
在 Swift callAsyncJavaScript()
中不可用。常规 JavaString mocklog
字符串中的 \n
在相应的 Swift 多行 String
文字中将是 \n
。
该文档有一个非常大的问题,因为它直接显示的代码不起作用,也不能它起作用。
var p = new Promise(function (f) {
window.setTimeout("f(42)", 1000);
});
首先,这是非常糟糕的做法。不要将字符串传递给 setTimeout
,因为它们将被评估为代码,这很危险并且很容易出错。而是传递一个函数。
当前的问题是,当您传递一个字符串时,它不会在当前范围内评估,而是在全局范围内评估:
const foo = "global";
function test() {
const foo = "local";
window.setTimeout("console.log(foo)", 1000);
}
test();
这意味着 window.setTimeout("f(42)", 1000);
不会调用函数参数 f
(解析函数)但会尝试使用名为 f
的全局变量并且很可能会失败,因为它不会存在。然后承诺永远悬而未决。
var p = new Promise(function (f) {
window.setTimeout("f(42)", 1000);
});
p
.then(value => console.log(`Completed successfully, value: ${value}`))
.catch(error => console.log(`Promise resulted in error`, error));
//neither of the above triggers because of the error in the console
创建稍后将通过 setTimeout
解决的承诺的正确方法是:
var p1 = new Promise(function (f) {
window.setTimeout(() => f(42), 1000);
});
var p2 = new Promise(function (f) {
window.setTimeout(f, 1000, 42);
});
var p3 = new Promise(function (f) {
window.setTimeout(f.bind(null, 42), 1000);
});
p1.then(value => console.log(`p1: Completed successfully, value: ${value}`));
p2.then(value => console.log(`p2: Completed successfully, value: ${value}`));
p3.then(value => console.log(`p3: Completed successfully, value: ${value}`));
参见:How can I pass a parameter to a setTimeout() callback?
基于WebKit的WKWebView documentation provides a callAsyncJavaScript() method有以下例子:
var p = new Promise(function (f) {
window.setTimeout("f(42)", 1000);
});
await p;
return p;
运行此示例代码模式的各种尝试return相同的“函数调用的完成处理程序不再可达”错误:
Error Domain=WKErrorDomain Code=4
"A JavaScript exception occurred"
UserInfo={
WKJavaScriptExceptionLineNumber=0,
WKJavaScriptExceptionMessage="Completion handler for function call is no longer reachable",
WKJavaScriptExceptionColumnNumber=0,
NSLocalizedDescription="A JavaScript exception occurred"
}
我已经用 callAsyncJavaScript
尝试对示例代码进行了一些调整。 callAsyncJavaScript
调用总是 return 出现相同的“...不再可达”错误。
这里有一个 运行 在浏览器 JS 控制台中没有错误的变体,它不适用于 callAsyncJavaScript
:
为什么会出错?这个例子能以某种方式成功地 运行 和 WKWebView
callAsyncJavaScript
吗?如果是,那又如何?
其他详细信息
问题可能是 Promise
在文档示例中没有正确解决。
下面的代码添加了 console.log()
跟踪信息。
let tracer = "a"
console.log("tracer:", tracer);
f = function (nProperty) {
let nResult = nProperty * 2
console.log("nResult:", nResult);
tracer = tracer + "d"
console.log("tracer:", tracer);
return nResult;
};
var p = new Promise(function (f) {
tracer = tracer + "b"
console.log("tracer:", tracer);
window.setTimeout("f(42)", 500);
});
console.log("BEFORE await:", p);
tracer = tracer + "c"
console.log("tracer:", tracer);
await p;
console.log("AFTER await:", p);
tracer = tracer + "e"
console.log("tracer:", tracer);
当在 Firefox JS 控制台中 运行 以上时,执行在 await 语句处停止。这是 Firefox JS 控制台输出:
tracer: a
tracer: ab
BEFORE await: Promise { <state>: "pending" }
tracer: abc
nResult: 84
tracer: abcd
语句 console.log("AFTER await:", p);
从未执行过。
还有一个细节...
从 Swift 程序员的角度来看,人们会期望从 Swift callAsyncJavaScript(…) 调用的成功 JavaScript 示例会 明确地 return 一个有用的结果 从 JavaScript 回到 Swift Result<Any, Error>)
.
请注意,callAsyncJavaScript(…)
作为异步 JavaScript 函数执行提供的 String
of JavaScript。
// do some JavaScript actions, await a result, and then..
return somthingUseful;
“函数调用的完成处理程序不再可达” 错误 callAsyncJavaScript() 文档示例中的问题可能是 Promise
没有 resolve
.
这是一个有效的 callAsyncJavaScript
示例(Swift 5.4、Xcode 15.5、macOS 11.4),它使用 Promise
、setTimeout
、await
并解析为 returned 数据对象。
function getPromise() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve({
'word': 'epistemology'
});
}, 2000);
});
}
function getResult() {
return getPromise()
.then(function(response) {
return response;
});
}
let data = await getResult()
.then(function(result) {
return result;
});
return data;
更新
阅读 VLAZ 提供的"f(32)"
破坏 Apple callAsyncJavaScript() 文档中提供的示例的真正原因(写在这个问题的时间)。
此外,Apple 示例在 FireFox JavaScript 控制台和 Swift callAsyncJavaScript(…)
方法,当根据 the answer provide by VLAZ.
从 JavaScript 到 Swift 的 return
值,如在 Swift LLDB 调试器中所见:
(lldb) po result
▿ Result<Any, Error>
- success : 42
附录:一些有用的提示
Swift callAsyncJavaScript()
方法 将提供的 JavaScript 字符串作为 WebKit 实例中的异步 JavaScript 函数 执行。因此,JavaScript 调试器和 console.log()
不适用于 Swift callAsyncJavaScript()
方法。
下面的方法可以帮助为计划的 JavaScript String
.
FireFox JavaScript 控制台可用于在 async
函数体中执行字符串:
// --- name browser window/tab ---
document.title = "Example 42";
async function callAsyncJavaScript() {
// --- begin Swift string literal section ---
let mocklog = "LOG: ";
var p = new Promise(function (f) {
mocklog += "A ";
window.setTimeout(() => f(42), 1000);
});
// p: Promise { <state>: "pending" }
mocklog += "B ";
let pResult = await p;
// p: Promise { <state>: "fulfilled", <value>: 42 }
mocklog += `C ${pResult}`;
return {"pResult": pResult, "mocklog": mocklog}
// --- end Swift string literal section ---
}
let result = await callAsyncJavaScript();
console.log("Result: ", result)
// Result: Object { pResult: 42, mocklog: "LOG: A B C 42" }
同一个JavaScript可以多行放置SwiftString
字面量:
let javaScript_42 = """
let mocklog = "LOG: ";
var p = new Promise(function (f) {
mocklog += "A ";
window.setTimeout(() => f(42), 1000);
});
// p: Promise { <state>: "pending" }
mocklog += "B ";
let pResult = await p;
// p: Promise { <state>: "fulfilled", <value>: 42 }
mocklog += `C ${pResult}`;
return {"pResult": pResult, "mocklog": mocklog}
"""
Swift 调试器结果:
(lldb) po result
▿ Result<Any, Error>
▿ success : 2 elements
▿ 0 : 2 elements
- key : pResult
- value : 42
▿ 1 : 2 elements
- key : mocklog
- value : LOG: A B C 42
脚注:添加了 mocklog
字符串作为 console.log()
的替代,因为 console.log
在 Swift callAsyncJavaScript()
中不可用。常规 JavaString mocklog
字符串中的 \n
在相应的 Swift 多行 String
文字中将是 \n
。
该文档有一个非常大的问题,因为它直接显示的代码不起作用,也不能它起作用。
var p = new Promise(function (f) {
window.setTimeout("f(42)", 1000);
});
首先,这是非常糟糕的做法。不要将字符串传递给 setTimeout
,因为它们将被评估为代码,这很危险并且很容易出错。而是传递一个函数。
当前的问题是,当您传递一个字符串时,它不会在当前范围内评估,而是在全局范围内评估:
const foo = "global";
function test() {
const foo = "local";
window.setTimeout("console.log(foo)", 1000);
}
test();
这意味着 window.setTimeout("f(42)", 1000);
不会调用函数参数 f
(解析函数)但会尝试使用名为 f
的全局变量并且很可能会失败,因为它不会存在。然后承诺永远悬而未决。
var p = new Promise(function (f) {
window.setTimeout("f(42)", 1000);
});
p
.then(value => console.log(`Completed successfully, value: ${value}`))
.catch(error => console.log(`Promise resulted in error`, error));
//neither of the above triggers because of the error in the console
创建稍后将通过 setTimeout
解决的承诺的正确方法是:
var p1 = new Promise(function (f) {
window.setTimeout(() => f(42), 1000);
});
var p2 = new Promise(function (f) {
window.setTimeout(f, 1000, 42);
});
var p3 = new Promise(function (f) {
window.setTimeout(f.bind(null, 42), 1000);
});
p1.then(value => console.log(`p1: Completed successfully, value: ${value}`));
p2.then(value => console.log(`p2: Completed successfully, value: ${value}`));
p3.then(value => console.log(`p3: Completed successfully, value: ${value}`));
参见:How can I pass a parameter to a setTimeout() callback?