起因

今天一个小程序客户接入我们的第三方js,没有正常执行,而我们自测是可以的。刚开始测试调节版本库发现大于等于1.9.2的就可以正常执行,低的就有问题。

debug

晚上在自己的环境上解除代码混淆,调试了一下发现是Promise的问题。

Promise.resolve(x).then(() => {
  console.log(‘这里没有执行’)
})

看了下代码,由于项目中用到了defer对象,所以Promise是用的第三方而不是原生的。而该第三方Promise实现用到了setImmediate。webpack打包时,监测到代码中使用了setImmediate,就会自动加入setImmediate polifill实现。该setImmediate实现在一系列降级中回退到了setTimeout,并且用到了setTimeout的第三个参数,下面是使用setTimeout第三个参数的例子

setTimeout((x) => {
  console.log(x)
}, 1000, '默认参数1')

在微信基础库版本1.9.2之前,这个参数没有被按标准实现。导致Promise出现异常。

解决方法

所以,最简单的方法就是在第三方Promise实现中去掉setImmediate的判断,直接用setTimeout(fn, 0)

或者,我们可以在webpack配置中,使用下面配置来关闭自动注入的setImmediate

{
...
node: false
}

或者,直接用微信原生的Promise,实现一个defer就可以了。

  Promise.defer = function () {
    const deferred = {}

    deferred.promise = new Promise(function (resolve, reject) {
      deferred.resolve = resolve
      deferred.reject = reject
    })

    return deferred
  }

总结

最后,可以学习下webpacksetImmediate实现,依次降级用到了Process.nextTick(micro)、postMessage(macro)、MessageChannel(macro)、script(macro)、setTimeout(macro)

    // Don't get fooled by e.g. browserify environments.
    if ({}.toString.call(global.process) === "[object process]") {
        // For Node.js before 0.9
        installNextTickImplementation();

    } else if (canUsePostMessage()) {
        // For non-IE10 modern browsers
        installPostMessageImplementation();

    } else if (global.MessageChannel) {
        // For web workers, where supported
        installMessageChannelImplementation();

    } else if (doc && "onreadystatechange" in doc.createElement("script")) {
        // For IE 6–8
        installReadyStateChangeImplementation();

    } else {
        // For older browsers
        installSetTimeoutImplementation();
    }

    function installNextTickImplementation() {
        registerImmediate = function(handle) {
            process.nextTick(function () { runIfPresent(handle); });
        };
    }

    function canUsePostMessage() {
        // The test against `importScripts` prevents this implementation from being installed inside a web worker,
        // where `global.postMessage` means something completely different and can't be used for this purpose.
        if (global.postMessage && !global.importScripts) {
            var postMessageIsAsynchronous = true;
            var oldOnMessage = global.onmessage;
            global.onmessage = function() {
                postMessageIsAsynchronous = false;
            };
            global.postMessage("", "*");
            global.onmessage = oldOnMessage;
            return postMessageIsAsynchronous;
        }
    }

    function installPostMessageImplementation() {
        // Installs an event handler on `global` for the `message` event: see
        // * https://developer.mozilla.org/en/DOM/window.postMessage
        // * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages

        var messagePrefix = "setImmediate$" + Math.random() + "$";
        var onGlobalMessage = function(event) {
            if (event.source === global &&
                typeof event.data === "string" &&
                event.data.indexOf(messagePrefix) === 0) {
                runIfPresent(+event.data.slice(messagePrefix.length));
            }
        };

        if (global.addEventListener) {
            global.addEventListener("message", onGlobalMessage, false);
        } else {
            global.attachEvent("onmessage", onGlobalMessage);
        }

        registerImmediate = function(handle) {
            global.postMessage(messagePrefix + handle, "*");
        };
    }

    function installMessageChannelImplementation() {
        var channel = new MessageChannel();
        channel.port1.onmessage = function(event) {
            var handle = event.data;
            runIfPresent(handle);
        };

        registerImmediate = function(handle) {
            channel.port2.postMessage(handle);
        };
    }

    function installReadyStateChangeImplementation() {
        var html = doc.documentElement;
        registerImmediate = function(handle) {
            // Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
            // into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
            var script = doc.createElement("script");
            script.onreadystatechange = function () {
                runIfPresent(handle);
                script.onreadystatechange = null;
                html.removeChild(script);
                script = null;
            };
            html.appendChild(script);
        };
    }

    function installSetTimeoutImplementation() {
        registerImmediate = function(handle) {
            setTimeout(runIfPresent, 0, handle);
        };
    }