回调
概述
- 为了演示回调、
promise
和其他抽象概念的使用,我们将使用一些浏览器方法:具体地说,是加载脚本和执行简单的文档操作的方法 JavaScript
主机(host
)环境提供了许多函数,这些函数允许我们计划 异步 行为(action
)—— 也就是在我们执行一段时间后才自行完成的行为- 例如,
setTimeout
函数就是一个这样的函数
- 例如,
- 让我们看一下函数
loadScript(src)
,该函数使用给定的src
加载脚本:- 它将一个新的、带有给定
src
的、动态创建的标签<script src="…">
插入到文档中 - 浏览器将自动开始加载它,并在加载完成后执行它
- 它将一个新的、带有给定
1 2 3 4 5 6 7 |
function loadScript(src) { // 创建一个 <script> 标签,并将其附加到页面 // 这将使得具有给定 src 的脚本开始加载,并在加载完成后运行 let script = document.createElement('script'); script.src = src; document.head.append(script); } |
- 脚本是“异步”调用的,因为它从现在开始加载,但是在这个加载函数执行完成后才运行
1 2 |
// 在给定路径下加载并执行脚本 loadScript('/my/script.js'); |
- 脚本是“异步”调用的,因为它从现在开始加载,但是在这个加载函数执行完成后才运行
- 假设我们需要在新脚本加载后立即使用它。它声明了新函数,我们想运行它们
- 但如果我们在
loadScript(…)
调用后立即执行此操作,这将不会有效
1 2 3 4 |
loadScript('/my/script.js'); // loadScript 下面的代码 // 不会等到脚本加载完成才执行 // ... |
1 2 3 |
loadScript('/my/script.js'); // 这个脚本有 "function newFunction() {…}" newFunction(); // 没有这个函数! |
- 自然情况下,浏览器可能没有时间加载脚本
- 到目前为止,
loadScript
函数并没有提供跟踪加载完成的方法。脚本加载并最终运行,仅此而已 - 但我们希望了解脚本何时加载完成,以使用其中的新函数和变量
- 让我们添加一个
callback
函数作为loadScript
的第二个参数,该函数应在脚本加载完成时执行:
- 到目前为止,
1 2 3 4 5 6 7 8 |
function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(script); document.head.append(script); } |
onload
事件
- 它通常会在脚本加载和执行完成后执行一个函数
- 现在,如果我们想调用该脚本中的新函数,我们应该将其写在回调函数中:
1 2 3 4 5 |
loadScript('/my/script.js', function() { // 在脚本加载完成后,回调函数才会执行 newFunction(); // 现在它工作了 ... }); |
- 这是我们的想法:第二个参数是一个函数(通常是匿名函数),该函数会在行为(
action
)完成时运行- 这是一个带有真实脚本的可运行的示例:
- 这被称为“基于回调”的异步编程风格
- 异步执行某项功能的函数应该提供一个
callback
参数用于在相应事件完成时调用
1 2 3 4 5 6 7 8 9 10 11 |
function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(script); document.head.append(script); } loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => { alert(`酷,脚本 ${script.src} 加载完成`); alert( _ ); // _ 是所加载的脚本中声明的一个函数 }); |
在回调中回调
- 如何依次加载两个脚本:第一个,然后是第二个?
- 自然的解决方案是将第二个
loadScript
调用放入回调中,如下所示: - 在外部
loadScript
执行完成时,回调就会发起内部的loadScript
- 自然的解决方案是将第二个
1 2 3 4 5 6 7 8 9 |
loadScript('/my/script.js', function(script) { alert(`酷,脚本 ${script.src} 加载完成,让我们继续加载另一个吧`); loadScript('/my/script2.js', function(script) { alert(`酷,第二个脚本加载完成`); }); }); |
- 如果我们还想要一个脚本呢?
- 因此,每一个新行为(
action
)都在回调内部 - 这对于几个行为来说还好,但对于许多行为来说就不好了
- 因此,每一个新行为(
1 2 3 4 5 6 7 8 9 10 11 |
loadScript('/my/script.js', function(script) { loadScript('/my/script2.js', function(script) { loadScript('/my/script3.js', function(script) { // ...加载完所有脚本后继续 }); }); }); |
处理 Error
- 上述示例中,我们并没有考虑出现
error
的情况- 如果脚本加载失败怎么办?我们的回调应该能够对此作出反应
- 这是
loadScript
的改进版本,可以跟踪加载错误 - 加载成功时,它会调用
callback(null, script)
,否则调用callback(error)
1 2 3 4 5 6 7 8 9 |
function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(null, script); script.onerror = () => callback(new Error(`Script load error for ${src}`)); document.head.append(script); } |
- 用法
- 我们在
loadScript
中所使用的方案其实很普遍。它被称为“Error
优先回调(error-first callback
)”风格 - 约定是:
callback
的第一个参数是为 error 而保留的。一旦出现 error,callback(err)
就会被调用- 第二个参数(和下一个参数,如果需要的话)用于成功的结果。此时
callback(null, result1, result2…)
就会被调用 - 因此,单一的
callback
函数可以同时具有报告 error 和传递返回结果的作用
- 我们在
1 2 3 4 5 6 7 |
loadScript('/my/script.js', function(error, script) { if (error) { // 处理 error } else { // 脚本加载成功 } }); |
回调地狱
- 乍一看,它像是一种可行的异步编程方式
- 的确如此,对于一个或两个嵌套的调用看起来还不错
- 但对于一个接一个的多个异步行为,代码将会变成这样:
- 随着调用嵌套的增加,代码层次变得更深,维护难度也随之增加,尤其是我们使用的是可能包含了很多循环和条件语句的真实代码,而不是例子中的
...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
loadScript('1.js', function(error, script) { if (error) { handleError(error); } else { // ... loadScript('2.js', function(error, script) { if (error) { handleError(error); } else { // ... loadScript('3.js', function(error, script) { if (error) { handleError(error); } else { // ...加载完所有脚本后继续 (*) } }); } }); } }); |
- 所以这种编码方式不是很好
- 我们可以通过使每个行为都成为一个独立的函数来尝试减轻这种问题,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
loadScript('1.js', step1); function step1(error, script) { if (error) { handleError(error); } else { // ... loadScript('2.js', step2); } } function step2(error, script) { if (error) { handleError(error); } else { // ... loadScript('3.js', step3); } } function step3(error, script) { if (error) { handleError(error); } else { // ...加载完所有脚本后继续 (*) } } |
Promise
构造器语法
Promise
对象的构造器(constructor
)语法如下:- 传递给
new Promise
的函数被称为executor
- 当
new Promise
被创建,executor
会自动运行。它包含最终应产出结果的生产者代码 - 它的参数
resolve
和reject
是由JavaScript
自身提供的回调。我们的代码仅在executor
的内部
- 传递给
1 2 3 |
let promise = new Promise(function(resolve, reject) { // executor(生产者代码,“歌手”) }); |
-
当
executor
获得了结果,无论是早还是晚都没关系,它应该调用以下回调之一:resolve(value)
—— 如果任务成功完成并带有结果value
reject(error)
—— 如果出现了error
,error
即为error
对象
-
总结:
executor
会自动运行并尝试执行一项工作- 尝试结束后,如果成功则调用
resolve
,如果出现error
则调用reject
-
由
new Promise
构造器返回的promise
对象具有以下内部属性:state
—— 最初是"pending"
,然后在resolve
被调用时变为"fulfilled"
,或者在reject
被调用时变为"rejected"
result
—— 最初是undefined
,然后在resolve(value)
被调用时变为value
,或者在reject(error)
被调用时变为error
示例
- 下面是一个
promise
构造器和一个简单的executor
函数,该executor
函数具有包含时间(即setTimeout
)的“生产者代码”:executor
被自动且立即调用(通过new Promise
)executor
接受两个参数:resolve
和reject
这些函数由JavaScript
引擎预先定义,因此我们不需要创建它们。我们只需要在准备好(译注:指的是executor
准备好)时调用其中之一即可
1 2 3 4 5 6 |
let promise = new Promise(function(resolve, reject) { // 当 promise 被构造完成时,自动执行此函数 // 1 秒后发出工作已经被完成的信号,并带有结果 "done" setTimeout(() => resolve("done"), 1000); }); |
- 下面则是一个
executor
以error
拒绝promise
的示例:
1 2 3 4 |
let promise = new Promise(function(resolve, reject) { // 1 秒后发出工作已经被完成的信号,并带有 error setTimeout(() => reject(new Error("Whoops!")), 1000); }); |
- 总而言之,
executor
应该执行一项工作(通常是需要花费一些时间的事儿),然后调用resolve
或reject
来改变对应的promise
对象的状态
注意1
- 只有一个结果或一个
error
executor
只能调用一个resolve
或一个reject
。任何状态的更改都是最终的- 所有其他的再对
resolve
和reject
的调用都会被忽略:
1 2 3 4 5 6 |
let promise = new Promise(function(resolve, reject) { resolve("done"); reject(new Error("…")); // 被忽略 setTimeout(() => resolve("…")); // 被忽略 }); |
- 以
Error
对象reject
- 如果什么东西出了问题,
executor
应该调用reject
- 这可以使用任何类型的参数来完成(就像
resolve
一样) - 但建议使用
Error
对象(或继承自Error
的对象)。这样做的理由很快就会显而易见
- 如果什么东西出了问题,
resolve/reject
可以立即进行- 实际上,
executor
通常是异步执行某些操作,并在一段时间后调用resolve/reject
- 但这不是必须的。我们还可以立即调用
resolve
或reject
,就像这样: - 例如,当我们开始做一个任务,随后发现一切都已经完成并已被缓存时,可能就会发生这种情况
- 实际上,
1 2 3 4 |
let promise = new Promise(function(resolve, reject) { // 不花时间去做这项工作 resolve(123); // 立即给出结果:123 }); |
state
和result
都是内部的Promise
对象的state
和result
属性都是内部的- 我们无法直接访问它们。但我们可以对它们使用
.then
/.catch
/.finally
方法
消费者:then,catch
Promise
对象充当的是executor
(“生产者代码”或“歌手”)和消费函数(“粉丝”)之间的连接,后者将接收结果或error
- 可以通过使用
.then
和.catch
方法注册消费函数.then
的第一个参数是一个函数,该函数将在promise resolved
且接收到结果后执行.then
的第二个参数也是一个函数,该函数将在promise rejected
且接收到error
信息后执行
1 2 3 4 |
promise.then( function(result) { /* handle a successful result */ }, function(error) { /* handle an error */ } ); |
1 2 3 4 5 6 7 8 9 10 |
let promise = new Promise(function(resolve, reject) { setTimeout(() => resolve("done!"), 1000); }); // resolve 运行 .then 中的第一个函数 promise.then( result => alert(result), // 1 秒后显示 "done!" error => alert(error) // 不运行 ); |
- 在
reject
的情况下,运行第二个:
1 2 3 4 5 6 7 8 9 |
let promise = new Promise(function(resolve, reject) { setTimeout(() => reject(new Error("Whoops!")), 1000); }); // reject 运行 .then 中的第二个函数 promise.then( result => alert(result), // 不运行 error => alert(error) // 1 秒后显示 "Error: Whoops!" ); |
- 如果我们只对成功完成的情况感兴趣,那么我们可以只为
.then
提供一个函数参数:
1 2 3 4 5 |
let promise = new Promise(resolve => { setTimeout(() => resolve("done!"), 1000); }); promise.then(alert); // 1 秒后显示 "done!" |
catch
- 如果我们只对
error
感兴趣,那么我们可以使用null
作为第一个参数:.then(null, errorHandlingFunction)
- 或者我们也可以使用
.catch(errorHandlingFunction)
,其实是一样的:
1 2 3 4 5 6 |
let promise = new Promise((resolve, reject) => { setTimeout(() => reject(new Error("Whoops!")), 1000); }); // .catch(f) 与 promise.then(null, f) 一样 promise.catch(alert); // 1 秒后显示 "Error: Whoops!" |
.catch(f)
调用是.then(null, f)
的完全的模拟,它只是一个简写形式
清理:finally
- 就像常规
try {...} catch {...}
中的finally
子句一样,promise
中也有finally
- 调用
.finally(f)
类似于.then(f, f)
,因为当promise settled
时f
就会执行:无论promise
被resolve
还是reject
finally
的功能是设置一个处理程序在前面的操作完成后,执行清理/终结- 例如,停止加载指示器,关闭不再需要的连接等
1 2 3 4 5 6 7 |
new Promise((resolve, reject) => { /* 做一些需要时间的事,之后调用可能会 resolve 也可能会 reject */ }) // 在 promise 为 settled 时运行,无论成功与否 .finally(() => stop loading indicator) // 所以,加载指示器(loading indicator)始终会在我们继续之前停止 .then(result => show result, err => show error) |
- 请注意,
finally(f)
并不完全是then(f,f)
的别名- 它们之间有重要的区别:
finally
处理程序(handler
)没有参数。在finally
中,我们不知道promise
是否成功。没关系,因为我们的任务通常是执行“常规”的完成程序(finalizing procedures)finally
处理程序将结果或error
“传递”给下一个合适的处理程序finally
处理程序也不应该返回任何内容。如果它返回了,返回的值会默认被忽略
此规则的唯一例外是当finally
处理程序抛出error
时。此时这个error
(而不是任何之前的结果)会被转到下一个处理程序
- 正如我们所看到的,第一个
promise
返回的value
通过finally
被传递给了下一个then
- 这非常方便,因为
finally
并不意味着处理一个promise
的结果
- 这非常方便,因为
1 2 3 4 5 |
new Promise((resolve, reject) => { setTimeout(() => resolve("value"), 2000) }) .finally(() => alert("Promise ready")) // 先触发 .then(result => alert(result)); // <-- .then 显示 "value" |
- 下面是一个
promise
返回结果为error
的示例,让我们看看它是如何通过finally
被传递给catch
的:
1 2 3 4 5 |
new Promise((resolve, reject) => { throw new Error("error"); }) .finally(() => alert("Promise ready")) // 先触发 .catch(err => alert(err)); // <-- .catch 显示这个 error |
注意2
- 我们可以对
settled
的promise
附加处理程序- 如果
promise
为pending
状态,.then/catch/finally
处理程序(handler
)将等待它的结果 - 有时候,当我们向一个
promise
添加处理程序时,它可能已经settled
了 - 在这种情况下,这些处理程序会立即执行:
- 如果
1 2 3 4 |
// 下面这 promise 在被创建后立即变为 resolved 状态 let promise = new Promise(resolve => resolve("done!")); promise.then(alert); // done!(立刻显示) |
示例:loadScript
- 接下来,让我们看一下关于
promise
如何帮助我们编写异步代码的更多实际示例 - 这是基于回调函数的变体,记住它:
1 2 3 4 5 6 7 8 9 |
function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(null, script); script.onerror = () => callback(new Error(`Script load error for ${src}`)); document.head.append(script); } |
- 用
promise
重写它- 新函数
loadScript
将不需要回调。取而代之的是,它将创建并返回一个在加载完成时resolve
的promise
对象 - 外部代码可以使用
.then
向其添加处理程序(订阅函数):
- 新函数
1 2 3 4 5 6 7 8 9 10 11 |
function loadScript(src) { return new Promise(function(resolve, reject) { let script = document.createElement('script'); script.src = src; script.onload = () => resolve(script); script.onerror = () => reject(new Error(`Script load error for ${src}`)); document.head.append(script); }); } |
1 2 3 4 5 6 7 8 |
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"); promise.then( script => alert(`${script.src} is loaded!`), error => alert(`Error: ${error.message}`) ); promise.then(script => alert('Another handler...')); |
promise
区别与callback
promise
允许我们按照自然顺序进行编码- 而在调用
loadScript(script, callback)
时,我们必须有一个callback
函数可供使用- 换句话说,在调用
loadScript
之前,我们必须知道如何处理结果
- 换句话说,在调用
Promise
链
概述
- 它的想法是通过
.then
处理程序(handler
)链进行传递result
- 初始
promise
在1
秒后resolve
(*)
- 然后
.then
处理程序被调用(**)
,它又创建了一个新的promise
(以2
作为值resolve
) - 下一个
then
(***)
得到了前一个then
的值,对该值进行处理(*2
)并将其传递给下一个处理程序
- 初始
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); // (*) }).then(function(result) { // (**) alert(result); // 1 return result * 2; }).then(function(result) { // (***) alert(result); // 2 return result * 2; }).then(function(result) { alert(result); // 4 return result * 2; }); |
- 这样之所以是可行的,是因为每个对
.then
的调用都会返回了一个新的 promise,因此我们可以在其之上调用下一个.then
- 当处理程序返回一个值时,它将成为该
promise
的result
,所以将使用它调用下一个.then
- 当处理程序返回一个值时,它将成为该
- 新手常犯的一个经典错误:从技术上讲,我们也可以将多个
.then
添加到一个promise
上。但这并不是promise
链(chaining
)- 如下
- 这里所做的只是一个
promise
的几个处理程序。它们不会相互传递result
;相反,它们之间彼此独立运行处理任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
let promise = new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); }); promise.then(function(result) { alert(result); // 1 return result * 2; }); promise.then(function(result) { alert(result); // 1 return result * 2; }); promise.then(function(result) { alert(result); // 1 return result * 2; }); |
返回 promise
.then(handler)
中所使用的处理程序(handler
)可以创建并返回一个promise
- 在这种情况下,其他的处理程序将等待它
settled
后再获得其结果 - 例如:
- 这里第一个
.then
显示1
并在(*)
行返回new Promise(…)
1
秒后它会进行resolve
,然后result
(resolve
的参数,在这里它是result*2
)被传递给第二个.then
的处理程序- 这个处理程序位于
(**)
行,它显示2
,并执行相同的行为
- 在这种情况下,其他的处理程序将等待它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); }).then(function(result) { alert(result); // 1 return new Promise((resolve, reject) => { // (*) setTimeout(() => resolve(result * 2), 1000); }); }).then(function(result) { // (**) alert(result); // 2 return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 2), 1000); }); }).then(function(result) { alert(result); // 4 }); |
示例:loadScript
- 按顺序依次加载脚本:
1 2 3 4 5 6 7 8 9 10 11 |
function loadScript(src) { return new Promise(function(resolve, reject) { let script = document.createElement('script'); script.src = src; script.onload = () => resolve(script); script.onerror = () => reject(new Error(`Script load error for ${src}`)); document.head.append(script); }); } |
1 2 3 4 5 6 7 8 |
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"); promise.then( script => alert(`${script.src} is loaded!`), error => alert(`Error: ${error.message}`) ); promise.then(script => alert('Another handler...')); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
loadScript("/article/promise-chaining/one.js") .then(function(script) { return loadScript("/article/promise-chaining/two.js"); }) .then(function(script) { return loadScript("/article/promise-chaining/three.js"); }) .then(function(script) { // 使用在脚本中声明的函数 // 以证明脚本确实被加载完成了 one(); two(); three(); }); |
- 可以用箭头函数来重写代码,让其变得简短一些:
- 在这儿,每个
loadScript
调用都返回一个promise
,并且在它 resolve 时下一个.then
开始运行 - 然后,它启动下一个脚本的加载
- 所以,脚本是一个接一个地加载的
- 在这儿,每个
1 2 3 4 5 6 7 8 9 |
loadScript("/article/promise-chaining/one.js") .then(script => loadScript("/article/promise-chaining/two.js")) .then(script => loadScript("/article/promise-chaining/three.js")) .then(script => { // 脚本加载完成,我们可以在这儿使用脚本中声明的函数 one(); two(); three(); }); |
Thenables
- 确切地说,处理程序返回的不完全是一个
promise
,而是返回的被称为 “thenable
” 对象 —— 一个具有方法.then
的任意对象。它会被当做一个promise
来对待 - 这个想法是,第三方库可以实现自己的“
promise
兼容(promise-compatible
)”对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Thenable { constructor(num) { this.num = num; } then(resolve, reject) { alert(resolve); // function() { native code } // 1 秒后使用 this.num*2 进行 resolve setTimeout(() => resolve(this.num * 2), 1000); // (**) } } new Promise(resolve => resolve(1)) .then(result => { return new Thenable(result); // (*) }) .then(alert); // 1000ms 后显示 2 |
更复杂的示例:fetch
- 在前端编程中,
promise
通常被用于网络请求- 执行这条语句,向
url
发出网络请求并返回一个promise
- 当远程服务器返回
header
(是在 全部响应加载完成前)时,该promise
使用一个response
对象来进行resolve
- 为了读取完整的响应,我们应该调用
response.text()
方法:当全部文字内容从远程服务器下载完成后,它会返回一个promise
,该promise
以刚刚下载完成的这个文本作为result
进行resolve
- 执行这条语句,向
1 |
let promise = fetch(url); |
1 2 3 4 5 6 7 8 9 10 11 |
fetch('/article/promise-chaining/user.json') // 当远程服务器响应时,下面的 .then 开始执行 .then(function(response) { // 当 user.json 加载完成时,response.text() 会返回一个新的 promise // 该 promise 以加载的 user.json 为 result 进行 resolve return response.text(); }) .then(function(text) { // ……这是远程文件的内容 alert(text); // {"name": "iliakan", "isAdmin": true} }); |
- 从
fetch
返回的response
对象还包含response.json()
方法,该方法可以读取远程数据并将其解析为JSON
1 2 3 4 |
// 同上,但使用 response.json() 将远程内容解析为 JSON fetch('/article/promise-chaining/user.json') .then(response => response.json()) .then(user => alert(user.name)); // iliakan,获取到了用户名 |
- 现在,让我们用加载好的用户信息搞点事情
- 例如,我们可以再向
GitHub
发送一个请求,加载用户个人资料并显示头像:
- 例如,我们可以再向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 发送一个对 user.json 的请求 fetch('/article/promise-chaining/user.json') // 将其加载为 JSON .then(response => response.json()) // 发送一个到 GitHub 的请求 .then(user => fetch(`https://api.github.com/users/${user.name}`)) // 将响应加载为 JSON .then(response => response.json()) // 显示头像图片(githubUser.avatar_url)3 秒(也可以加上动画效果) .then(githubUser => { let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); setTimeout(() => img.remove(), 3000); // (*) }); |
- 请看
(*)
行:我们如何能在头像显示结束并被移除 之后 做点什么?- 例如,我们想显示一个用于编辑该用户或者其他内容的表单。就目前而言,是做不到的
- 为了使链可扩展,我们需要返回一个在头像显示结束时进行
resolve
的promise
- 就像这样:
- 也就是说,第
(*)
行的.then
处理程序现在返回一个new Promise
,只有在setTimeout
中的resolve(githubUser)
(**)
被调用后才会变为settled
- 链中的下一个
.then
将一直等待这一时刻的到来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
fetch('/article/promise-chaining/user.json') .then(response => response.json()) .then(user => fetch(`https://api.github.com/users/${user.name}`)) .then(response => response.json()) .then(githubUser => new Promise(function(resolve, reject) { // (*) let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); setTimeout(() => { img.remove(); resolve(githubUser); // (**) }, 3000); })) // 3 秒后触发 .then(githubUser => alert(`Finished showing ${githubUser.name}`)); |
- 作为一个好的做法,异步行为应该始终返回一个
promise
- 这样就可以使得之后我们计划后续的行为成为可能
- 最后,我们可以将代码拆分为可重用的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
function loadJson(url) { return fetch(url) .then(response => response.json()); } function loadGithubUser(name) { return loadJson(`https://api.github.com/users/${name}`); } function showAvatar(githubUser) { return new Promise(function(resolve, reject) { let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); setTimeout(() => { img.remove(); resolve(githubUser); }, 3000); }); } // 使用它们: loadJson('/article/promise-chaining/user.json') .then(user => loadGithubUser(user.name)) .then(showAvatar) .then(githubUser => alert(`Finished showing ${githubUser.name}`)); // ... |
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!