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;}
  }

    
}
posted @ 2025-02-01 14:55  zhongta  阅读(13)  评论(0)    收藏  举报