Skip to content

Commit

Permalink
Merge pull request #478 from NYUCCL/doc-describe-async-preload-pages
Browse files Browse the repository at this point in the history
docs: describe the new preloadPages approach
  • Loading branch information
deargle committed Mar 23, 2021
2 parents 3219016 + 2bd8391 commit b970847
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 26 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -42,6 +42,8 @@ thought through.
- `def regularpage` in experiment.py no longer calls flask's `render_template` -- instead, it sends the
file as-is. If you need a custom template to be rendered, then create a route for your template in `custom.py`, and
call `render_template()` on it yourself.
- psiturk.js `preloadPages()` now returns a `Promise`. See the [migration guide](https://psiturk.readthedocs.io/en/latest/migrating.html) for links to
examples of using the new approach.

#### Configuration files
- the configuration file created by running `psiturk-setup-example`
Expand Down
57 changes: 39 additions & 18 deletions doc/api.rst
Expand Up @@ -67,29 +67,42 @@ useful for debugging. For example:
psiturk.taskdata.get('condition');
.. _api-preload-pages:

``psiturk.preloadPages(pagelist)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For each path in ``pagelist``, this will request the html and store in
the ``psiturk`` object. A given page can then be loaded later using
``psiturk.getPage(pagename)``.

Returns a `Promise`__. See the `example task.js`_ for a full usage example.

__ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

.. _example task.js: https://github.com/NYUCCL/psiTurk/blob/master/psiturk/example/static/js/task.js

Example:

.. code-block:: javascript
// Preload a set of HTML files
psiturk.preLoadPages(['instructions.html', 'block1.html', 'block2.html']);
async function example() {
await psiturk.preloadPages(['instructions.html', 'block1.html', 'block2.html']);
// Set the content of the body tag to one of the pages
$('body').html(psiturk.getPage('block1.html'));
}
example()
// Set the content of the body tag to one of the pages
$('body').html(psiturk.getPage('block1.html'));
``psiturk.getPage(pagename)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Retrieve a stored HTML object that has been preloaded using
``psiturk.preLoadPages``.
``psiturk.preloadPages``.


``psiturk.showPage(pagename)``
Expand All @@ -102,8 +115,11 @@ Example:

.. code-block:: javascript
psiturk.preloadPages(['instructions.html', 'block1.html', 'block2.html');
psiturk.showPage('instructions.html');
async function example() {
psiturk.preloadPages(['instructions.html', 'block1.html', 'block2.html');
psiturk.showPage('instructions.html');
}
example()
``psiturk.preloadImages(imagelist)``
Expand Down Expand Up @@ -223,18 +239,23 @@ Example

.. code-block:: javascript
psiturk = new PsiTurk(uniqueId, adServerLoc);
var pages = [
"instructions/instruct-1.html",
"instructions/instruct-2.html",
"instructions/instruct-3.html"];
psiTurk.preloadPages(pages); // preload the pages
var instructionPages = [ // any file here should be preloaded first
"instructions/instruct-1.html",
"instructions/instruct-2.html",
"instructions/instruct-3.html"]; // however, you can have as many as you like
psiturk.doInstructions(instructionPages,
function() { currentview = new StroopExperiment(); });
async function start_experiment(){
psiturk = new PsiTurk(uniqueId, adServerLoc);
var pages = [
"instructions/instruct-1.html",
"instructions/instruct-2.html",
"instructions/instruct-3.html"];
await psiTurk.preloadPages(pages); // preload the pages
var instructionPages = [ // any file here should be preloaded first
"instructions/instruct-1.html",
"instructions/instruct-2.html",
"instructions/instruct-3.html"]; // however, you can have as many as you like
psiturk.doInstructions(instructionPages,
function() { currentview = new StroopExperiment(); });
}
start_experiment()
The last line in this example uses an anonymous function
to launch the `Stroop Experiment <stroop.html>`__.
Expand Down
31 changes: 23 additions & 8 deletions doc/migrating.rst
@@ -1,6 +1,6 @@
.. _migrating:

Migrating from psiTurk 2.0 to 3.0
Migrating from psiTurk 2 to 3
=================================

Announcing psiTurk 3.0
Expand All @@ -12,28 +12,28 @@ psiTurk 2.0 launched on April 28, 2014 and there has been (according to github)
commits since then to the project from a wide variety of contributors. The evolution and
longevity of the project really has exceeded anything the original authors thought
would be possible. In the seven years since 2.0 was first tagged so many things have changed
about web experimentation, web application development, the Amazon turk API, and even the
about web experimentation, web application development, the Amazon turk API, and even the
Python ecosystem.

One part of psiTurk that has always been both a blessing and a curse is the reliance on
several services provided by xxx.psiturk.org. This includes the "Ad server" and several other
One part of psiTurk that has always been both a blessing and a curse is the reliance on
several services provided by xxx.psiturk.org. This includes the "Ad server" and several other
api elements that were envisioned to distribute timely information to users of the system.
Generally this seemed like a good engineering solution to a problem, but centralization is generally
bad because if something happened to psiturk.org (annual SSL certs renew on time) then the system
goes down for everyone. Every year during conference deadlines the original project creator
would lose sleep.

As a result, a major change in psiTurk 3.0 is to make the system decoupled from the services on
psiturk.org. It is possible now to easily get a SSL signed connection to a cloud-based server (e.g.,
psiturk.org. It is possible now to easily get a SSL signed connection to a cloud-based server (e.g.,
heroku) and to run the server in a "headless" mode. This gets around the need for the psiturk.org
"Ad server" which was written in 2014 and has basically never been updated since. In a way this
"Ad server" which was written in 2014 and has basically never been updated since. In a way this
means psiTurk acts more like a traditional Flask "web application" rather than an interactive
command line tool, although the command line interface remains for interacting with Amazon and
command line tool, although the command line interface remains for interacting with Amazon and
for development.

In addition to the hard decoupling work (led really by Dave Eargle), version 3.0 offers several new features
including a "campaign mode" which allows you to run a certain number of subject (say 100) by repeatedly
posting lower cost 9 assignment HITs, and a dashboard for managing hits. In addition, a major change
posting lower cost 9 assignment HITs, and a dashboard for managing hits. In addition, a major change
since 2.0 is support for Python 3.0 and the changes that entailed to work with more recent
versions of boto Mturk python api.

Expand Down Expand Up @@ -87,6 +87,21 @@ No More Python 2
psiTurk 3 drops support for python 2, for various reasons. See the changelog_ for
more details.


Optimized psiturk.js preloadPages()
-----------------------------------

``psiturk.js``'s ``preloadPages()`` now returns a javascript `Promise`__.
It does this so that it can simultaneously start to load *all* of
the pages, rather than one at a time. The function will ``resolve``
when *all* pages have finished preloading. Look in the `example's task.js`__ for
more detailed comments on how you need to refactor your ``task.js`` to use the
new ``preloadPages()``. Also see :ref:`the api page for preloadPages <api-preload-pages>`
for other examples.

__ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
__ https://github.com/NYUCCL/psiTurk/blob/master/psiturk/example/static/js/task.js

Detailed changelog
------------------

Expand Down
18 changes: 18 additions & 0 deletions psiturk/example/static/js/task.js
Expand Up @@ -21,6 +21,16 @@ var pages = [
"postquestionnaire.html"
];

// In javascript, defining a function as `async` makes it return a `Promise`
// that will "resolve" when the function completes. Below, `init` is assigned to be the
// *returned value* of immediately executing an anonymous async function.
// This is done by wrapping the async function in parentheses, and following the
// parentheses-wrapped function with `()`.
// Therefore, the code within the arrow function (the code within the curly brackets) immediately
// begins to execute when `init is defined. In the example, the `init` function only
// calls `psiTurk.preloadPages()` -- which, as of psiTurk 3, itself returns a Promise.
//
// The anonymous function is defined using javascript "arrow function" syntax.
const init = (async () => {
await psiTurk.preloadPages(pages);
})()
Expand Down Expand Up @@ -221,6 +231,14 @@ var currentview;
/*******************
* Run Task
******************/
// In this example `task.js file, an anonymous async function is bound to `window.on('load')`.
// The async function `await`s `init` before continuing with calling `psiturk.doInstructions()`.
// This means that in `init`, you can `await` other Promise-returning code to resolve,
// if you want it to resolve before your experiment calls `psiturk.doInstructions()`.

// The reason that `await psiTurk.preloadPages()` is not put directly into the
// function bound to `window.on('load')` is that this would mean that the pages
// would not begin to preload until the window had finished loading -- an unnecessary delay.
$(window).on('load', async () => {
await init;
psiTurk.doInstructions(
Expand Down

0 comments on commit b970847

Please sign in to comment.