javascript chpater 13 异步
13.1.1 Timer
let updateIntervalId=setInterval(checkForUpdates,60000);
function stopCheckingForUpdates(){
clearInterval(updateIntervalId);
}
事件
let okay=document.querySelector('#confirmUpdateDialog button.okay');
okay.addEventListener('click',applyUpdate);
applyUpdate是一个假设的callback函数。当用户点击了指定区域后会执行这个函数。
13.1.3 网络事件
function getCurrentVersionNumber(versionCallback){
let request=new XMLHttpRequest();
request.open("GET","http://www.example.com/api/version");
request.send();
request.onload=function(){
if(request.status==200){//good status
let currentVersion=parseFloat(request.responseText);
versionCallback(null,currentversion);
}else{
versionCallback(response.statusText,null);
}
};
request.onerror=request.ontimeout=function(e){
versionCallback(e.type,null);
};
}
13.2Promises
Promise是一个代表异步计算结果的对象
Promise代表了单个异步计算的未来结果,不能用于重复异步计算。
getJSON(url).then(jsonData=>{
/*This is a callback function that will be asynchronously invoked with the parsed JSON value when it becomes available*/
});
getJSON()开始于一个异步HTTP请求,请求Url。当request待处理时,返回一个Promise对象。Promise对象定义了一个then()实例方法。我们不直接把callback函数交给getJSON(),而是交给then()方法。当HTTP请求到达时,HTTP body会被转化为JSON,转化结果会传递给then()中,我们定义的函数。
你可以把then()方法想象为addEventListener()方法,用于注册句柄。
Promise对象代表了一次计算,then()方法注册的函数应该只被调用一次。
例
function displayUserProfile(profile){}//用于获得用户信息的函数
getJSON("/api/user/profile").then(displayUserProfile);//注册then()
用Promise处理错误。
办法是传递第二个函数给then()方法。
getJSON("/api/user/profile").then(displayUserProfile,handleProfileError);
一种更加地道的解决方法是
getJSON("/api/user/profile").then(displayUserProfile).catch(handleProfileError);
XMLHttpEquest对象现在已经不在使用,而是用fetch()API代替。
使用fetch()传递给他一个url,他会返回一个Promise
例
fetch("/api/user/profile").then(response=>{
if(response.ok&& response.headers.get("Content-Type")==="application/json")
//We don't actually have the response body yet.
}
});
当被fetch()返回的Promise被填充后,它会将一个Response对象传递给由你定义并传递给的then()方法的函数。这个response对象可以访问request status
和headers,这个对象也定义了text()和json()方法。但是尽管Promise已经被填充,响应体确不一定到达。所以那些为了访问响应体的text()和json()方法也返回
Promise对象。下面是一种幼稚的访问HTTP体的方法。
fetch("/api/user/profile").then(response=>{
response.json().then(profile=>{
displayUserProfile(profile);
}
}
});
使用序列链的代码
fetch("/api/user/profile")
.then(response=>{
return response.json();
})
.then(profile=>{
displayUserProfile(profile);
});
方法调用逻辑是fetch().then().then()。我们把这种逻辑叫做序列链。
fetch(theURL) //return promise1
.then(callback1) //return promise2
.then(callback2); //return promise 3
实现细节:
1.第一行代码,fetch()被调用,传递url参数给它,它初始化了该url的HTTP GET 请求,并返回一个Promise 我们称它为Promise1
2.第二行,调用了then()方法,当Promise1被填充后,就会调用callback1方法,并返回一个Promise2
3.第三行,调用then()方法并传递callback2,then()方法会返回一个Promise,我们会获得Promise3
4.表达式运行时,步骤1-3会同时进行,现在我们在step 1运行并在Internet上传递数据时,异步的暂停。
5.最终,HTTP相应会到达,fetch()函数会打包HTTP status,headers到Response对象,填充Promise1,Response对象作为其值。
6.当Promise1被填充后,它的值(Response对象)会被传递给callback1()函数,然后第二步开始,这一步的任务是,将Response对象作为输入,
获得reponse体的一个JSON对象。
7.假设第二步执行正常,它会用JSON对象填充Promise2
8.Promise2会被作为第三步的输入,传递给callback2函数,第三步目标是将数据显示给用户,然后Promise3被填充。序列链结束。
Promise chain 一般使用.catch()方法处理异常。下述方法等价
p.then(null,c);
p.catch(c);
ES2018定义了.finally()方法,作用类似于捕捉异常的try/catch/finally
上边代码的改进
fetch("/api/user/profile").then(response=>{
if(!response.ok)
return null;
//check the header to ensure the server sent us JSON
let type=response.headers.get("content-type");
if(type!=="application/json"){
throw new TypeError(`Expected JSON,got ${type}`);
}
//If we get here we got a 2xx status and Json content-type
return response.json();
})
.then(profile=>{
if(profile)
displayUserProfile(profile);
else
displayLoggedOutProfilePage();
})
.catch(e=>{
if(e instanceof NetworkError){
displayErrorManage("Check your Internet connection.");
}
else if(e instance of TypeError){
displayErrorMessage("Something is wrong with out server!");
}else{
console.log(e);
}
});
如果Promises 链中出现了可以恢复的错误,可以利用.catch()
startAsyncOperation()
.then(doStageTwo)
.catch(recoverFromStageTwoError)
.then(doStageThree)
.then(doStageFour)
.catch(logStageThreeAndFourErrors);
如果网络有故障,可以使用catch进行 重试,例如数据库查询
queryDatabase()
.catch(e=>wait(500).then(queryDatabase))
.then(displayTable)
.catch(displayDatabaseError);
13.2.5 Promises in Parallel
Promise.all()
Promise.all() 用Promise数组作为输入,一个Promise对象作为返回值。返回的值会是rejected,如果任何一个input Promise是rejected的。
换句话说,它会填充所有输入的Promise对象。
例:如果你想获得多个URL的内容,你可以使用下面的代码。
const urls=[/*url array put here*/];
promises=urls.map(url=>fetch(url).then(r=>r.next()));
Promise.all(promises)
.then(bodies=>{/*do something with the array of strings*/}
.catch(e=>console.error(e));
Promise.all()的输入数组可以是Promise对象,也可以是非Promise的值。如果元素值不是Promise对象,那么它会认为这个值属于一个已经填充的Promise,它只会简单的拷贝这个值到输出数组中。
Promise.allSettled()也是把Promise数组作为输入,但是不会rejected 返回Promise对象。它只在所有的输入Promise都被解决后才会填充返回Promise对象。
Promise.allSettled([Promise.resolve(1),Promise.reject(2),3]).then(results=>{
results[0]//{status:"fulfilled",value:1}
results[0]//{status:"rejected",value:2}
results[0]//{status:"fulfilled",value:1}
});
如果你只关心第一个返回值,使用Promise.race()
13.2.6 制造Promises
.then()方法会返回Promise,所以getJSON()可写为
function getJSON(url){
return fetch(url).then(response=>response.json());
}
下面利用getJSON()书写一个Promise返回函数
function getHighScore(){
return getJSON("/api/user/profile").then(profile=>profile.highScore);
}
这个函数假设服务器会返回一个profile,且其中含有highScore。
Promise.resolve()接收一个值作为参数,返回一个rejected的Promise,接受的值作为拒绝的理由。
注意resolved的Promise,与fulfilled填充的Promise并不相同。当我们调用Promise.resolve()时,我们传递填充值从而创建一个Promise对象,这个对象会迅速填充这个值。如果你将一个Promise p1传递给Promise.resolve(),它会返回一个新的Promise p2。
Promise.reject()适用场景是你在异步通信开始时检测到一个错误,你可以报告这个错误并使用reject函数返回Promise对象。
Promise()构造函数
Promise()构造函数需要传递一个函数作为它的参数。这个函数要有两个参数,resolve reject。当构造函数调用了你传递的函数后,会生成Promise对象。
你传递的函数需要进行一些异步通信操作,调用resolve函数去填充Promise或调用reject取reject返回的Promise。你的函数不需要是异步的,它可以同时调用热死了呵和reject,但是Promise需要被置为解决,填充,或拒绝。
例:
function wait(duration){
return new Promise((resolve,reject)=>{
if(duration<0)
reject(new Error("Time travel not yet implemented"));
//otherwise wait asynchronously and then resolve the Promise
//set timeout will invoke resolve() with no arguments,witch means that the Promise will fulfill with undefined value
});
}
例2:
const http=require("http");
function getJSON(url){
return new Promise((resolve,reject)=>{
request=http.get(url,response=>{
if(response.statusCode!==200){
reject(new Error(`HTTP status ${response.statusCode}`));
response.resume();//so we don't leak memory
}
//And reject if the response headers are wrong
else if(response.headers["content-type"]!="application/json"){
reject(new Error("Invalid content-type"));
response.resume();
}
else{
//success
let body="";
response.setEncoding("utf-8");
response.on("data",chunk=>{body+=chunk;});
response.on("end",()=>{
try{
let parsed=JSON.parse(body);
resolve(parsed);
} catch(e){
reject(e);
}
});
});
}
如果你要fetch多个Url,但为了防止网络拥塞一次只fetch一个,Url的数组元素个数是未知的,fetch()的内容也未知,你需要动态生成,可以使用如下代码。
function fetchSequentially(urls){
const bodies=[];
function fetchOne(url){
return fetch(url)
.then(response=>response.text())
.then(body=>{
bodies.push(body);
});
}//end function fetchOne
let p=Promise.resolve(undefined);
for(url of urls)
p=p.then((()=>fetchOne(url));
//when the last promise in that chain is fulfilled ,then the bodies array is ready,So let's return a Promise for that bodies array.
return p.then(()=>bodies);
}//end fetchSequentially
使用
fetchSequentially(urls)
.then(bodies=>{/*do something with the array of strings */}
.catch(e=>console.error(e));
13.3 async and await
13.3.1 await表达式
await关键字获取一个Promise,将其作为返回值并抛出异常
let response =await fetch("/api/user/profile");
let profile=await response.json();
使用await的代码是异步的。
13.3.2 async 函数
你只能在async函数中使用await。
async function getHighScore(){
let response =await fetch("/api/user/profile");
let profile=await response.json();
return profile.highScore;
}
声明async关键字的函数,返回值是Promise对象。成功返回时是正常值,失败会返回被拒绝rejected 的Promise对象。
13.3.3 awaiting multiple promises
async function getJSON(url){
let response=await fetch(url);
let body=await response.json();
return body;
}
let value1=await getJSON(url1);
let value2=await getJSON(url2);
上述代码在获得第一个结果后才会获得第二个结果。如果我们要并行的话使用:
let [value1,value2]=await Promise.all([getJSON(url1),getJSON(url2)]);
13.4异步迭代
Promise不适用于一系列的异步事件。ES2018提供了解决方法,for/await
13.4.1
const fs=require("fs");
async function parseFile(filename){
let stream=fs.createReadStream(filename,{encoding:"utf-8");
for await(let chunck of stream){
parseChunk(chunk);//Assume parseChunk() is defind elsewhere
}
}
for/await循环基于Promise。粗略的讲,异步迭代器产生了Promise,而for/await 循环等待Promise被填充。然后赋值给循环变量,然后执行循环体。执行完循环体后等待另一个Promise被填充,继续循环。
下两种方法等价:
for(const promise of promises){
response=await response;
handle(response);
}
for await(const response of promises){
handle(response);
}
注意上述方法必须放置在声明了async的函数中。
13.4.2异步迭代器Iterator
回顾普通迭代器,实现了Symbol.iterator,返回一个iterator对象。而next()方法返回一个迭代结果对象。
异步可迭代对象(asynchronously iterable object)实现了Symbol.asyncIterator,而next()方法返回一个Promise对象。
13.4.3异步产生器(Asynchronous Generators)
使用async关键字声明一个产生器函数从而实现迭代器功能。
//A promise-based wrapper around set Timeout() that we can use await with,return a Promise that fulfills in the specified number of
//milliseconds
function elapsedTime(ms){
return new Promise(resolve=>setTimeout(resolve,ms);
}
//An async generator function that increments a counter and yields it
//a specified number of times at a specified interval.
async function* clock(interval,max=Infinity){
for(let count =1;count<=max;count++){
for(let count=1;count<=max;count++){
await elapsedTime(interval);
yield count;
}
}
}
async function test(){
for await(let tick of clock(300,100)){
console.log(tick);
}
}
13.4.4 实现异步迭代器
定义Symbol.asyncIterator()方法,返回带有next()方法的对象,该对象返回Promise对象。
function clock(interval,max=Infinity){
function until(time){
return new Promise(resolve=>setTimeout(resolve,time-Date.now()));
}
return{
startTime:Date.now();
count:1;
async next(){
if(this.count>max){
return {done:true};
}
let targetTime=this.startTime+tihs.count*interval;
await until(targetTime);
return{value:this.count++};
},
[Symbol.asyncIterator(){return this;}
}
}

浙公网安备 33010602011771号