axios自定义的jsonp适配器

回到文章
<p>打开network面板可以看到这两个请求的发起方式是不一样的</p>
<p>一个是通过请求js产生的,一个是XMLHttpRequest产生的</p>
function isAbsoluteURL(url) {
        // A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
        // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
        // by any combination of letters, digits, plus, period, or hyphen.
        return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);
    }

    /**
     * Creates a new URL by combining the specified URLs
     *
     * @param {string} baseURL The base URL
     * @param {string} relativeURL The relative URL
     * @returns {string} The combined URL
     */
    function combineURLs(baseURL, relativeURL) {
        return relativeURL ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') : baseURL;
    }

    function buildFullPath(baseURL = '', requestedURL = '') {
        if (baseURL && !isAbsoluteURL(requestedURL)) {
            return combineURLs(baseURL, requestedURL);
        }
        return requestedURL;
    }
    function buildURL(url, params, paramsSerializer) {
        /*eslint no-param-reassign:0*/
        if (!params) {
            return url;
        }

        var serializedParams;
        var parts = [];
        for (var key in params) {
            parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`);
        }
        serializedParams = parts.join('&');

        if (serializedParams) {
            var hashmarkIndex = url.indexOf('#');
            if (hashmarkIndex !== -1) {
                url = url.slice(0, hashmarkIndex);
            }

            url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
        }

        return url;
    }

    // 这里的config是axios里所有的配置
    var jsonpAdapter = (config) => {
        return new Promise((resolve, reject) => {
            // 是否已取消当前操作
            // 因jsonp没有主动取消请求的方式
            // 这里使用 isAbort 来标识
            var isAbort = false;

            // 定时器标识符
            var timer = null;

            // 执行方法的名字,
            var callbackName = `jsonp${Date.now()}_${Math.random().toString().slice(2)}`;

            // 这里假设已经实现了baseURL和url的拼接方法
            var fullPath = buildFullPath(config.baseURL, config.url);

            // 这里假设已经实现了url和参数的拼接方法
            // 不太一样的地方在于,jsonp需要额外插入一个自己的回调方法
            var url = buildURL(
                fullPath,
                {
                    ...config.params,
                    ...{ [config.jsonpCallback || 'callback']: callbackName },
                },
                config.paramsSerializer
            );

            // 创建一个script标签
            var script = document.createElement('script');

            // 成功执行操作后
            function remove() {
                if (script) {
                    script.onload = script.onerror = null;

                    // 移除script标签
                    if (script.parentNode) {
                        script.parentNode.removeChild(script);
                    }
                    // 取消定时器
                    if (timer) {
                        clearTimeout(timer);
                    }

                    script = null;
                }
            }

            // 成功请求后
            window[callbackName] = (data) => {
                // 若已需要请求,则不再执行
                if (isAbort) {
                    return;
                }

                // 返回的格式
                var response = {
                    status: 200,
                    statusText: 'ok',
                    config,
                    request: script,
                    data: data,
                };
                remove();
                // 实际上这里上一个settle操作,会额外判断是否是合理的status状态
                // 若我们在config.validateStatus中设置404是合理的,也会进入到resolve状态
                // 但我们这里就不实现这个了
                // settle(resolve, reject, response);
                resolve(response);
            };

            // 请求失败
            script.onerror = function (error) {
                remove();

                reject(createError('Network Error', config, 404));
            };

            // 若设置了超时时间
            if (config.timeout) {
                timer = setTimeout(function () {
                    remove();
                    // 取消当前操作
                    isAbort = true;
                    reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 405));
                }, config.timeout);
            }

            // 若定义了取消操作
            if (config.cancelToken) {
                config.cancelToken.promise.then(function () {
                    if (!script) {
                        return;
                    }
                    remove();
                    isAbort = true;

                    reject(createError('Cancel Error', config, 404));
                });
            }

            script.src = url;
            var target = document.getElementsByTagName('script')[0] || document.head;
            target.parentNode && target.parentNode.insertBefore(script, target);
        });
    };

    var request = axios.create({
        adapter: function (config) {
            if (config.params && config.params.format === 'jsonp') {
                return jsonpAdapter(config);
            }

            // 这里需要将config.adapter设置为空
            // 否则会造成无限循环
            return axios(Object.assign({}, config, { adapter: undefined }));
        },
    });
    // 使用自定义的适配器jsonp发起请求
    request('https://api.prize.qq.com/v1/newsapp/answer/share/oneQ?qID=506336', {
        params: {
            format: 'jsonp',
        },
    })
        .then(function (response) {
            console.log('jsonp response', response);
        })
        .catch(function (error) {
            console.error('jsonp error', error);
        });

    // 使用axios默认的适配器发起请求
    request('https://api.prize.qq.com/v1/newsapp/answer/share/oneQ?qID=506336')
        .then(function (response) {
            console.log('axios response', response);
        })
        .catch(function (error) {
            console.error('axios error', error);
        });