ntscshen / ntscshen.github.io

个人博客
0 stars 2 forks source link

JS经典interview(二) - Promise化( AJAX&&JSONP ) #3

Open ntscshen opened 7 years ago

ntscshen commented 7 years ago

jQuery方法写Ajax,这是jQuery栈的必备知识。最近刷题 - 发现对Ajax原生的概念有点模糊。

对Ajax理解的最大困难在于:http\tcp。

什么是Ajax?为什么要使用Ajax?

Ajax是浏览器专门用来和服务器进行交互的异步通讯技术( 核心是XMLHttpRequest )。 Ajax是不刷新页面 - 请求数据的唯一办法。

ntscshen commented 7 years ago

使用原生封装了Ajax

function ajax(json) {
    json = json || {}; // 如果没有数据传入则 - 赋值{}
    if (!json.url) { // 如果没传递url - 则返回
        return;
    }

    json.data = json.data || {};
    json.type = json.type || 'get'; // 默认提交方式GET
    // 获取ajax
    var XHR = new XMLHttpRequest();
    // 判断传递的类型
    switch (json.type.toLowerCase()) {
        case 'get':
            XHR.open('GET', json.url + '?' + getData(json.data), true);
            // 发送数据
            XHR.send();
            break;
        case 'post':
            XHR.open('POST', json.url, true);
            XHR.setRequestHeader('content-type', 'application/x-www-form-urlencoded'); // 设置请求头信息
            XHR.send(getData(json.data));
            break;
    }

    // 接收数据 - 状态监控
    XHR.onreadystatechange = function() {
        if (XHR.readyState === 4) {
            if (XHR.status >= 200 && XHR.status < 300 || XHR.status == 304) {
                // 执行成功的回调函数
                json.success && json.success(JSON.parse(XHR.responseText));
            } else {
                json.error && json.error(XHR.status);
            }
        }
    }
}

// 对传入的参数做处理
function getData(json) {
    var arr = [];
    for (var name in json) {
        arr.push((encodeURIComponent(name) + '=' + encodeURIComponent(json[name])));
        // encodeURIComponent // 为了处理中文
    }
    return arr.join('&');
}

Promise化

function ajax(options) {
    return new Promise((resolve, reject) => {
        let {
            url = new Error('url must a string'),
                method = 'get',
                dataType = 'text',
                data = null,
                success,
                error
        } = options;
        // 获取ajax
        var XHR = new XMLHttpRequest();
        // 判断传递的类型
        switch (options.type.toLowerCase()) {
            case 'get':
                XHR.open('GET', `${url}?${getData(options.data)}`, true);
                XHR.send();
                break;
            case 'post':
                XHR.open('POST', options.url, true);
                XHR.setRequestHeader('content-type', 'application/x-www-form-urlencoded'); // 设置请求头信息
                XHR.send(getData(options.data));
                break;
        }
        XHR.responseType = dataType;
        // 接收数据 - 状态监控
        XHR.onreadystatechange = function() {
            if (XHR.readyState === 4) {
                if (XHR.status >= 200 && XHR.status < 300 || XHR.status == 304) {
                    // 执行成功的回调函数
                    options.success && options.success(options.parse(XHR.responseText));
                    resolve(options.parse(XHR.responseText));
                } else {
                    options.error && options.error(XHR.status);
                    reject(XHR.status);
                }
            }
        }
        // 错误
        XHR.onerror = function(err) {
            error && error(err);
            reject(err);
        }
    });
}

// 对传入的参数做处理
function getData(options) {
    var arr = [];
    for (var name in options) {
        arr.push((encodeURIComponent(name) + '=' + encodeURIComponent(options[name])));
        // encodeURIComponent // 为了处理中文
    }
    return arr.join('&');
}

// 使用
ajax({
    url: '/login',
    method: 'post',
    dataType: 'json',
    data: {'name': 'ntscshen','age': '25'},
}).then(data => {
    console.log(data);
}).catch(e => {
    console.log(e);
});
ntscshen commented 7 years ago

原生封装的 JSONP


// JSONP
function jsonp(options) {
    options = options || {};
    if (!options.url || !options.callback) {
        throw new Error("参数不合法");
    }
    // 创建script标签并加入页面中
    var callbackName = ('jsonp_' + Math.random()).replace(".", "");
    var oHead = document.getElementsByTagName('head')[0];
    var oScript = document.createElement('script');
    oHead.appendChild(oScript);

    options.data[options.callback] = callbackName;
    var params = formatParams(options.data); // 格式化数据

    // 创建jsonp回调函数
    // callbackName是一个变量 - 而且必须是全局变量 - 因此window[callbackName]怎么去使用
    // script标签的src属性只在第一次设置的时候起作用,导致script标签没法重用。每次完成操作之后都需要移除。
    window[callbackName] = function(json) {
        options.success && options.success(json);
        oHead.removeChild(oScript);
        try {
            delete window[callbackName];
        } catch (e) {}
        window[callbackName] = null;
    }
    // 发送请求
    oScript.src = options.url + (options.url.indexOf("?") > -1 ? "" : "?") + params;
}

// 数据参数格式化
function formatParams(data) {
    var arr = [];
    var str = '';
    for (var key in data) {
        if (data.hasOwnProperty(key)) {
            // arr.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[i]));
            str += encodeURIComponent(key) + '=' + encodeURIComponent(data[key]) + "&";
        }
    }
    return str;
    // return arr.join('&');
}
ntscshen commented 7 years ago
// url中的data数据格式化 - 优化
function formatParams(data) {
  let str = ''
  for (let key in data) {
    if (data.hasOwnProperty(key)) { // 遍历对象所有属性,忽略掉继承属性
      // 判断传递的参数中是否有undefined如果有则置空,否则原有数据不变
      let value = data[key] === undefined ? '' : data[key]
      str += `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`
    }
  }
  // 删除第一个&
  return str ? str.substring(1) : ''
}
    // 调用
    formatParams({
        'name': 'ntscshen',
        'format': 'jsonp',
        'jsonpCallback': 'jsonp1'
    })
function xxx(rul, data){
    url += (url.indexOf('?') < 0 ? '?' : '&') + formatParams(data)
}
ntscshen commented 7 years ago
// new一个Promise对象,并没有调用它,我们传递进去的函数就已经被执行了。这是一个很重要的细节点,所以我们用Promise的时候一般是包在一个函数中,在需要的时候去运行这个函数
// Promise :优势在于,可以在then方法中继续写Promise对象并返回,让后继续调用thne来进行回调操作
// Promise不仅仅是简化了 层层回调的写法
// 实质上,Promise的精髓在于"状态",用户维护状态、传递状态的方式来使得回调函数能够及时被调用。比callback简单、灵活

  function runAsync(){
    return new Promise((resolve, reject) => {
      // 做异步操作
      setTimeout(() => {
        console.log('异步任务、执行完成');
        resolve('数据')
      }, 1000)
    })
  }
  function runAsync1(){
    return new Promise((resolve, reject) => {
      setTimeout(function(){
        console.log('异步任务1、执行完成');
        resolve('数据1')
      }, 1000)
    })
  }
  function runAsync2(){
    return new Promise((resolve, reject) => {
      setTimeout(function(){
        console.log('异步任务2、执行完成');
        resolve('数据2')
      }, 1000)
    })
  }
    // 调用
    runAsync().then((data) => {
      console.log(data);
      return runAsync1();
    }).then((data) => {
      console.log(data);
    })

简易的Promise实现

zentanso commented 7 years ago

建议看一下ES6 的Generator函数以及async await,Promise还不是异步的最终答案

ntscshen commented 6 years ago

过渡:Generator函数

// Generator生成器 => 迭代器
function* gen() {
  let a = yield new Promise();
  let b = yield new Promise();
  return b;
}
gen().next().value.then(data => {
  // let a 中的promise
  // next传递給let b
  gen().next(data).value.then(data => {
    console.log(gen().next(data).value.toString());
  });
});

实际:Generator + bluebird + co

// co原理
// 1、co链式调用(Promise)
// 2、异步迭代
function co(gen) {
  return new Promise((resolve, reject) => {
    function step() {
      let { value, done } = gen.next(data); // {value: 'XXX', done: false}
      if (!done) {
        value.then(data => {
          step(data); // 成功后迭代
        }, reject);// reject有一次失败即失败
      } else { // 成功
        resolve(value);
      }
    }
    step();
  });
}
ntscshen commented 6 years ago

async + await

  1. Generator + co 的语法糖
  2. async 函数的返回结果是 promiseawait 后面必须跟着 promise
  3. await 只能出现在 async 函数中
  4. async await 中是同步阻塞的异步执行 其并不能代替 promise 。 如果多个请求并发请使用 Promise.all([xx,xx,xx]);

为什么说async+await是 generator + co的语法糖

// babel在编译时,会把async转换成了generator。 这也许是答案

function _asyncToGenerator(fn) {
  return function () {
    var gen = fn.apply(this, arguments);
    return new Promise(function (resolve, reject) {
      function step(key, arg) {
        try {
          var info = gen[key](arg);
          var value = info.value;
        } catch (error) {
          reject(error); return;
        }

        if (info.done) {
          resolve(value);
        } else {
          return Promise.resolve(value).then(function (value) {
            step("next", value);
          }, function (err) {
            step("throw", err);
          });
        }
      }
      return step("next");
    });
  };
}