本文通过实践从古至今对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+


seasonley
607 声望693 粉丝

一切皆数据