FE.ES-非同步程式設計進化史

seasonley發表於2018-12-15

本文通過實踐從古至今對XMLHttpRequest封裝的各種方式,來了解在es中非同步程式設計的實現細節和設計模式。

回顧XMLHttpRequest原生寫法

XMLHttpRequest -MDN

var xhr = new XMLHttpRequest()
xhr.timeout = 2000
xhr.open("POST", "path/to/api")
xhr.onload= function () {console.log(xhr.status,xhr.responseText)}
xhr.setRequestHeader("Content-type","text/plain;charset=UTF-8")
xhr.send(`{"key":"value"}`)

常見封裝方法

1.回撥函式

傳值方式實現

function request(method, url, done) {
  var xhr = new XMLHttpRequest()
  xhr.open(method, url)
  xhr.onload = function () {
    if (this.status >= 200 && this.status < 300)
        done(null, xhr.response)
    else
        done(xhr.response)
  }
  xhr.onerror = function () {done(xhr.response)}
  xhr.send()
}
//---
request(`GET`, `path/to/api`, function (err, res) {
  if (err){console.error(err)}
  console.log(res)
})

傳物件方式實現

function request(obj){
    var xhr = new XMLHttpRequest()
    xhr.open(obj.method, obj.url)
    xhr.onload= function () {
        if (this.status >= 200 && this.status < 300)
            obj.success(xhr.response)
        else
            obj.fail(xhr.response)
    }
    xhr.onerror= function () {obj.fail(xhr.response)}
    for(let key in obj.header){xhr.setRequestHeader(key ,obj.header[key])}
    xhr.send(obj.data)
}
//---
request({
    url: `path/to/api`,
    data: {},
    method:"GET",
    header: {`Content-type`: `application/json`},
    success(res) {console.log(res)},
    fail(err) {console.error(err)}
})

2.Promise

使用 Promise – MDN
Promise 本質上是一個繫結了回撥的物件,而不是將回撥傳進函式內部。
傳值方式實現

function requestPromise(method, url) {
  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest()
    xhr.open(method, url)
    xhr.onload = function () {
      if (this.status >= 200 && this.status < 300)
        resolve(xhr.response)
      else
        reject({status: this.status,statusText: xhr.statusText})
    }
    xhr.onerror = function () {
      reject({status: this.status,statusText: xhr.statusText})
    }
    xhr.send()
  })
}
// ---
requestPromise(`GET`, `path/to/api`)
.then(function (res1) {
  return makeRequest(`GET`, res1.url)
})
.then(function (res2) {
  console.log(res2)
})
.catch(function (err) {
  console.error(err)
});

傳物件方式實現

function makeRequest (opts) {
  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.open(opts.method, opts.url);
    xhr.onload = function () {
      if (this.status >= 200 && this.status < 300) {
        resolve(xhr.response);
      } else {
        reject({
          status: this.status,
          statusText: xhr.statusText
        });
      }
    };
    xhr.onerror = function () {
      reject({
        status: this.status,
        statusText: xhr.statusText
      });
    };
    if (opts.headers) {
      Object.keys(opts.headers).forEach(function (key) {
        xhr.setRequestHeader(key, opts.headers[key]);
      });
    }
    var params = opts.params;
    // We`ll need to stringify if we`ve been given an object
    // If we have a string, this is skipped.
    if (params && typeof params === `object`) {
      params = Object.keys(params).map(function (key) {
        return encodeURIComponent(key) + `=` + encodeURIComponent(params[key]);
      }).join(`&`);
    }
    xhr.send(params);
  });
}

// Headers and params are optional
makeRequest({
  method: `GET`,
  url: `http://example.com`
})
.then(function (datums) {
  return makeRequest({
    method: `POST`,
    url: datums.url,
    params: {
      score: 9001
    },
    headers: {
      `X-Subliminal-Message`: `Upvote-this-answer`
    }
  });
})
.catch(function (err) {
  console.error(`Augh, there was an error!`, err.statusText);
});

3.fetch ,Request (基於promise)

fetch-MDN

fetch(`path/to/api`, {credentials: `include`})
.then(function(response) {
    if (response.status >= 400) {
        throw new Error("Bad response from server");
    }
    return response.json();
})
.then(function(stories) {
    console.log(stories);
});

Request-MDN

var myRequest = new Request(
                    `path/to/api`, 
                    { method: `GET`,
                       headers: {`Content-Type`: `application/json`},
                       mode: `cors`,
                       cache: `default` 
                   }
               )
fetch(myRequest).then(function(response) {
  //...
});

4.async/await (ES7)

async function -MDN
await -MDN

async function f1() {
  var res = await requestPromise(`GET`, `path/to/api`);
  console.log(res)
}
f1();

其他

sleep 實現 (async/await)

function sleep (time) {
    return new Promise(function (resolve, reject) {setTimeout(resolve, time)})
}

var start = async function () {
    for (var i = 1; i <= 10; i++) {
        console.log(`當前是第${i}次等待..`);
        await sleep(1000);
    }
};

start();

co+Generator

感覺是 async/await 到來之前的過渡方案

const co=require(`co`)
//let begin = new Date();
co(function* (){
    let buyed = yield buySomething();
    //console.log(buyed ,new Date() - begin);
    
    let cleaned = yield clean();
    //console.log(cleaned ,new Date() - begin);
    
    let cook_and_wash = yield [cook(),wash()];
    //console.log(cook_and_wash ,new Date() - begin);
    
    let eated = yield eat();
    //console.log(eated,new Date() - begin);
});

非同步流程控制庫

RxJS 中文文件

參考
XMLHttpRequest -MDN
使用 Promise – MDN
How do I promisify native XHR? – starkoverflow
深入理解ES7的async/await
fetch-MDN
Request-MDN
async function -MDN
await -MDN
RxJS 中文文件
Promises/A+

相關文章