GithubHelp home page GithubHelp logo

mo / abortcontroller-polyfill Goto Github PK

View Code? Open in Web Editor NEW
325.0 7.0 28.0 2.11 MB

Polyfill for the AbortController DOM API and abortable fetch (stub that calls catch, doesn't actually abort request).

License: MIT License

JavaScript 92.02% Shell 3.59% HTML 4.39%
javascript polyfill abortcontroller abortsignal fetch ponyfill

abortcontroller-polyfill's Introduction

AbortController polyfill for abortable fetch()

npm version

Minimal stubs so that the AbortController DOM API for terminating fetch() requests can be used in browsers that doesn't yet implement it. This "polyfill" doesn't actually close the connection when the request is aborted, but it will call .catch() with err.name == 'AbortError' instead of .then().

const controller = new AbortController();
const signal = controller.signal;
fetch('/some/url', {signal})
  .then(res => res.json())
  .then(data => {
    // do something with "data"
  }).catch(err => {
    if (err.name == 'AbortError') {
      return;
    }
  });
// controller.abort(); // can be called at any time

You can read about the AbortController API in the DOM specification.

How to use

$ npm install --save abortcontroller-polyfill

If you're using webpack or similar, you then import it early in your client entrypoint .js file using

import 'abortcontroller-polyfill/dist/polyfill-patch-fetch'
// or:
require('abortcontroller-polyfill/dist/polyfill-patch-fetch')

Using it on browsers without fetch

If you need to support browsers where fetch is not available at all (for example Internet Explorer 11), you first need to install a fetch polyfill and then import the abortcontroller-polyfill afterwards.

The unfetch npm package offers a minimal fetch() implementation (though it does not offer for example a Request class). If you need a polyfill that implements the full Fetch specification, use the whatwg-fetch npm package instead. Typically you will also need to load a polyfill that implements ES6 promises, for example promise-polyfill, and of course you need to avoid ES6 arrow functions and template literals.

Example projects showing abortable fetch setup so that it works even in Internet Explorer 11, using both unfetch and GitHub fetch, is available here.

Using it along with 'create-react-app'

create-react-app enforces the no-undef eslint rule at compile time so if your version of eslint does not list AbortController etc as a known global for the browser environment, then you might run into an compile error like:

  'AbortController' is not defined  no-undef

This can be worked around by (temporarily, details here) adding a declaration like:

  const AbortController = window.AbortController;

Using the AbortController/AbortSignal without patching fetch

If you just want to polyfill AbortController/AbortSignal without patching fetch you can use:

import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'

Using it on Node.js

You can either import it as a ponyfill without modifying globals:

const { AbortController, abortableFetch } = require('abortcontroller-polyfill/dist/cjs-ponyfill');
const { fetch } = abortableFetch(require('node-fetch'));
// or
// import { AbortController, abortableFetch } from 'abortcontroller-polyfill/dist/cjs-ponyfill';
// import _fetch from 'node-fetch';
// const { fetch } = abortableFetch(_fetch);

or if you're lazy

global.fetch = require('node-fetch');
require('abortcontroller-polyfill/dist/polyfill-patch-fetch');

If you also need a Request class with support for aborting you can do:

const { AbortController, abortableFetch } = require('abortcontroller-polyfill/dist/cjs-ponyfill');
const _nodeFetch = require('node-fetch');
const { fetch, Request } = abortableFetch({fetch: _nodeFetch, Request: _nodeFetch.Request});

const controller = new AbortController();
const signal = controller.signal;
controller.abort();
fetch(Request("http://api.github.com", {signal}))
  .then(r => r.json())
  .then(j => console.log(j))
  .catch(err => {
      if (err.name === 'AbortError') {
          console.log('aborted');
      }
  })

See also Node.js examples here

Using it on Internet Explorer 11 (MSIE11)

The abortcontroller-polyfill works on Internet Explorer 11. However, to use it you must first install separate polyfills for promises and for fetch(). For the promise polyfill, you can use the promise-polyfill package from npm, and for fetch() you can use either the whatwg-fetch npm package (complete fetch implementation) or the unfetch npm package (not a complete polyfill but it's only 500 bytes large and covers a lot of the basic use cases).

If you choose unfetch, the imports should be done in this order for example:

import 'promise-polyfill/src/polyfill';
import 'unfetch/polyfill';
import 'abortcontroller-polyfill';

See example code here.

Using it on Internet Explorer 8 (MSIE8)

The abortcontroller-polyfill works on Internet Explorer 8. However, since github-fetch only supports IE 10+ you need to use the fetch-ie8 npm package instead and also note that IE 8 only implements ES 3 so you need to use the es5-shim package (or similar). Finally, just like with IE 11 you also need to polyfill promises. One caveat is that CORS requests will not work out of the box on IE 8.

Here is a basic example of abortable fetch running in IE 8.

Contributors

License

MIT

abortcontroller-polyfill's People

Contributors

ambar avatar caub avatar dpwrussell avatar eps1lon avatar homer0 avatar jakechampion avatar jimmywarting avatar joaovieira avatar mo avatar rmja avatar sairamsrinivasan avatar silverwind avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

abortcontroller-polyfill's Issues

v1.6.0 breaks controller being used with multiple requests

When an AbortController is used with more than one request and aborted, only one of the requests is successfully aborted.

Test case: https://github.com/tekwiz/node-fetch/blob/feature/abort-controller-tests/test/main.js#L890
Test run: https://github.com/tekwiz/node-fetch/runs/1674440695?check_suite_focus=true#step:7:256

I attempted backing off the abortcontroller-polyfill package to 1.6.0, and the test still fails; however, backing off the abortcontroller-polyfill package to 1.5.0 fixes the issue.

Support web workers

While working on JakeChampion/fetch#592 and bringing this in to have a full-featured polyfill for testing, I noticed it doesn't work inside a web worker as it uses the dom to create an EventTarget. Would you be willing to rework the Emitter class to support that?

screen shot 2018-02-02 at 01 16 20

Safari's AbortController doesn't work

First of all, this is Safari's fault.

Safari 12.0.1 supports AbortController, so the polyfill is not applied, but it doesn't do anything.

Here's a really small example I made, with a delay on the request, just in case:

import 'whatwg-fetch';
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch';

const abortController = new AbortController();

const req = fetch(
  'https://jsonplaceholder.typicode.com/todos/1',
  { signal: abortController.signal }
)
.then((response) => new Promise((resolve) => {
  setTimeout(() => { resolve(response);  }, 1000);
}));

abortController.abort();

req
.then((response) => response.json())
.then((response) => console.log(response))
.catch((error) => { console.error(error); });

If you try this out on Chrome, it works like a charm, but on Safari doesn't.

Just to test if it was a Safari issue, I went to your package and removed the following check (to apply the polyfill even if there was support):

if (self.AbortController) {
  return;
}

And it worked.

Finally, the reason I'm opening the issue is to see if...

  1. Are you aware of the issue?
  2. Is possible to add an "extra check" to the browser implementation. By "extra check" I mean, you understand the real API better than me, so maybe you know what can be missing there and it can validated it before ignoring the polyfill.
  3. I missed something on the documentation... in which case I'm really sorry!.

Incompatible with NextJS middleware

Repro steps:

  1. Create a NextJS app with pnpm create next-app --ts
  2. Create a middleware.ts with import 'abortcontroller-polyfill/dist/polyfill-patch-fetch'
  3. Run pnpm i and pnpm dev
  4. Go to http://localhost:3000/

Expected result:
App runs

Actual result:
TypeError: Cannot redefine property: AbortController

It detects the environment as needing a polyfill via this line. The fetch property is missing, but it is unable to redefine the AbortController` property on the global object.

https://github.com/David-Rickard/abortcontroller-polyfill-nextjs-repro

Doesn't work in basic react-app

I've used create-react-app (which means Webpack under the hook!), and when I tried to play with this polyfill I had the following error:

Line 77:  'AbortController' is not defined  no-undef

I added some console.log in the abortcontroller script and saw nothing...

The Request constructor should copy the signal from the init argument

Something along this:

            const nativeProto = Request.prototype;
            const NativeRequest = Request;
            Request = function (input, init) {
                let request = new NativeRequest(input, init);
                if (init && init.signal) {
                    request.signal = init.signal;
                }
                return request;
            }
            Request.prototype = nativeProto;

Remove reference to babel preset "latest"

When I try to bundle this polyfill in my application I get the following error:

Error: Couldn't find preset "latest" relative to directory ".../node_modules/abortcontroller-polyfill"

Babel preset latest is deprecated and this project references babel-preset-env

No error occurs if I change it to "env" or remove the babel presets entry altogether.

Can the babel presets entry in package.json be changed to "env" or else removed (due to use of the .babelrc file)?

"babel": {
    "presets": [
      "latest"   <-- change to "env"
    ]
  },

Make compatible with Node.js

Hi, we are interested in using this module in node-fetch (basically a fetch() for Node.js), but it seems like this module does not yet support Node.js. Would you folks be opposed to a Node.js version using something like event-target-shim or Node.js' internal EventEmitter class that makes this package Node.js-compatible? Thanks!

AbortController does not seem to abort blob requests

I'm using a fetch request to get blob data from the server in the form of PNGs. If the request goes beyond a specific timeout that I've set, I am calling abort on the request (as per your instructions). The abort seems to run tough without errors, but looking at the network tab, the fetches still report "pending", eventually to return with 200 status. Is there something I'm missing?
Using updated Chrome on Linux Mint.

Polyfill breaking working implementation in Pale Moon browser

Because of the below prototype check a working implementation of AbortController in Pale Moon browser is overwritten with one that causes an exception.

  function polyfillNeeded(self) {
    if (self.__FORCE_INSTALL_ABORTCONTROLLER_POLYFILL) {
      console.log('__FORCE_INSTALL_ABORTCONTROLLER_POLYFILL=true is set, will force install polyfill');
      return true;
    } // Note that the "unfetch" minimal fetch polyfill defines fetch() without
    // defining window.Request, and this polyfill need to work on top of unfetch
    // so the below feature detection needs the !self.AbortController part.
    // The Request.prototype check is also needed because Safari versions 11.1.2
    // up to and including 12.1.x has a window.AbortController present but still
    // does NOT correctly implement abortable fetch:
    // https://bugs.webkit.org/show_bug.cgi?id=174980#c2


    return typeof self.Request === 'function' && !self.Request.prototype.hasOwnProperty('signal') || !self.AbortController;
  }

When using this polyfill any use of the signal in a fetch call will result in a 'signal' member of RequestInit does not implement interface AbortSignal. exception.

To reproduce the issue you can use this snippet:

var controller = new AbortController();
var signal = controller.signal;
fetch(location.href, {signal});
setTimeout(function() { controller.abort(); }, 50);

Request is never closed on server side ([email protected] + [email protected])

Simple sample code:

// Server implmentation
import express from 'express';
const app = express();
app.use((req, res, next) => {
   req.on('close', () => console.log('CLOSED'));
   next();
});
app.get('*', (req, res) => setTimeout(() => res.send('Ok'), 5000));
app.listen(808, () => console.log('http://localhost:808'));
// Client  code
let execute = async (fetch, AbortController) => {
    const ac = new AbortController();
    const rP = fetch('http://localhost:808', { signal: ac.signal });
    setTimeout(() => ac.abort(),1000);
    try {
        console.log(await rP);
    } catch (ex) {
        console.log(ex.name);
    }
};

When I run client code on browser:

   execute(fetch, AbortController)

On server console I see the 'CLOSED' on client 'AbortErrror'.

Now when I try to run client on node :

import { AbortController, AbortSignal, abortableFetch } from 'abortcontroller-polyfill/dist/cjs-ponyfill';
import nodeFetch from 'node-fetch';
const { fetch } = abortableFetch(nodeFetch);
execute(fetch, AbortController);

On client I see 'AbortErrror', but there is no any log on Server .

For compassion, if I use https://www.npmjs.com/package/abort-controller :

import AbortController from 'abort-controller';
import fetch from 'node-fetch';
execute(fetch, AbortController);

I have same result as on browser.

I lost half a day to finally find that 'abortcontroller-polyfill' is not support real request cancellation (I was looking for problem in my code :().
Maybe this is problem with this particular versions, I do not know.
Please fix that or at least add information about such limitation in documentation.
Best regards.

Support signal on the Request object

If the first argument to fetch is a Request object, then the polyfill should try and resolve the signal from there.

That is, in this line it is checked whether the Request object has the signal property, but in this line it is only checked whether the second argument has the property, and not the first if that is a Request object.

If AbortController.abort() directly precedes a Fetch() in code then the Fetch following it will be instantly aborted

I'm using your polyfill with whatwg-fetch.

I have something like this:

_this.fetchController.abort();

fetch(this.props.dataSource, {
          signal: _this.fetchController.signal,
          method: "POST",
          body: JSON.stringify({
            strict,
            page,

....

I would like to ensure that there's only one fetch request going out to that specific resource at any one point, so each user interaction would abort a running request and start a new one.

However what happens in practice is that AbortController will abort the running request and also the new one that would start right after it.

Abort event handler should execute immediately, not debounced

I've got the following snippet of code:

const controller = new AbortController();
controller.signal.addEventListener('abort', () => console.log('aborting'));
(function () {
  controller.abort();
  console.log('done aborting');
})();

When I run this in Chrome, I get the following output:

aborting
done aborting

But when I run this using this polyfill, I get the following:

done aborting
aborting

It looks like this is because the events handlers are debounced. If I were to use signal.onAbort = ... it would behave the same as Chrome, but I'd rather use the addEventListener handler as it avoids directly mutating signal.

Typescript module definitions

I'm trying to use this in a Typescript project, but am having to in an odd way because this library doesn't have type module definitions. Any plans to add this? I'm happy to spin up a PR if it's helpful.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.