One, what isPromise

1.1 PromisePast and present lives

Promise first appeared in 1988, by Barbara Liskov, Liuba Shrira initiative (paper: Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems). And there are already similar implementations in the languages MultiLisp and Concurrent Prolog.

In JavaScript, promises are popular thanks to the jQuery method jquery.deferred (), but there are also smaller, stand-alone Promise libraries such as Q, When, and Bluebird.

// Q / 2010
import Q from 'q'

function wantOdd () {
    const defer = Q.defer()
    const num = Math.floor(Math.random() * 10)
    if (num % 2) {
        defer.resolve(num)
    } else {
        defer.reject(num)
    }
    return defer.promise
}

wantOdd()
    .then(num= > {
        log(`Success: ${num} is odd.`) // Success: 7 is odd.
    })
    .catch(num= > {
        log(`Fail: ${num} is not odd.`)})Copy the code

The fact that jQuery doesn’t strictly follow the specification prompted A series of important official clarifications of Promise’s implementation, which was named Promise/A+. Later, ES6 (also called ES2015, officially released in June 2015) officially implemented A Promise interface on the Promise/A+ standard.

new Promise( function(resolve, reject) {... }/* Actuator */  );
Copy the code

To implement a Promise, the following rules must be followed:

  1. PromiseIs an offer that meetsstandardthethen()Method object.
  2. The initial state ispendingCan be converted tofulfilledrejectedState.
  3. Once thefulfilledrejectedThe state is determined and can no longer be converted to another state.
  4. Once the status is determined, a value must be returned, and the value is not modifiable.

ECMAScript’s Promise global is just one of many Promises/A+ implementations.

Mainstream language implementations of Promise: Golang/ Go-Promise, Python/ Promise, C#/ Real-serial-Games/C-sharp-Promise, PHP/Guzzle Promises, Java/IOU, Objective-C/PromiseKit, Swift/FutureLib, Perl/stevan/ Promises – Perl

To solve the problem

Because JavaScript is a single-threaded event-driven programming language, multiple tasks are managed through callback functions. In rapid iterative development, it is easy to create the infamous callback hell problem due to the abuse of callback functions. Promise’s asynchronous programming solution is more sensible and readable than callbacks.

Some of the more dramatic pullbacks in legend:

Callbacks with strong dependencies in real business:

// The callback function
function renderPage () {
    const secret = genSecret()
    // Get the user token
    getUserToken({
        secret,
        success: token= > {
            // Get the list of games
            getGameList({
                token,
                success: data= > {
                    // Render the list of games
                    render({
                        list: data.list,
                        success: () = > {
                            // Report the buried point data
                            report()
                        },
                        fail: err= > {
                            console.error(err)
                        }
                    })
                },
                fail: err= > {
                    console.error(err)
                }
            })
        },
        fail: err= > {
            console.error(err)
        }
    })
}
Copy the code

After using Promise grooming process:

// Promise
function renderPage () {
    const secret = genSecret()
    // Get the user token
    getUserToken(token)
        .then(token= > {
            // Get the list of games
            return getGameList(token)
        })
        .then(data= > {
            // Render the list of games
            return render(data.list) 
        })
        .then(() = > {
            // Report the buried point data
            report()
        })
        .catch(err= > {
            console.error(err)
        })
}
Copy the code

1.2 Implement a super simple version of the Promise

Promises actually operate in an observer pattern, with anonymous functions in then() acting as observers and Promise instances acting as observers.

const p = new Promise(resolve= > setTimeout(resolve.bind(null.'from promise'), 3000))

p.then(console.log.bind(null.1))
p.then(console.log.bind(null.2))
p.then(console.log.bind(null.3))
p.then(console.log.bind(null.4))
p.then(console.log.bind(null.5))
/ / after 3 seconds
// 1 2 3 4 5 from promise
Copy the code

/ / implementation
const defer = () = > {
    let pending = [] // Act as states and collect observers
    let value = undefined
    return {
        resolve: (_value) = > { // FulFilled!
            value = _value
            if (pending) {
                pending.forEach(callback= > callback(value))
                pending = undefined}},then: (callback) = > {
            if (pending) {
                pending.push(callback)
            } else {
                callback(value)
            }
        }
    }
}

/ / simulation
const mockPromise = () = > {
    let p = defer()
    setTimeout(() = > {
        p.resolve('success! ')},3000)
    return p
}

mockPromise().then(res= > {
    console.log(res)
})

console.log('script end')
// script end
/ / after 3 seconds
// success!
Copy the code

Second,PromiseHow to use

2.1 the use ofPromiseAsynchronous programming

Callbacks were used to manage the state of some asynchronous programs before a Promise came along.

// Common asynchronous Ajax request format
ajax(url, successCallback, errorCallback)
Copy the code

Use THEN () to receive the state of an event after a Promise appears, and only once.

Example: Plug-in initialization.

Using the callback function:

// Plugin code
let ppInitStatus = false
let ppInitCallback = null
PP.init = callback= > {
    if (ppInitStatus) {
        callback && callback(/ * * / data)}else {
        ppInitCallback = callback
    }
}
// ...
// ...
// The initialization is completed after a series of synchronous asynchronous programs
ppInitCallback && ppInitCallback(/ * * / data)
ppInitStatus = true

// Third party call
PP.init(callback)
Copy the code

Promise of use:

// Plugin code
let initOk = null
const ppInitStatus = new Promise(resolve= > initOk = resolve)
PP.init = callback= > {
    ppInitStatus.then(callback).catch(console.error)
}
// ...
// ...
// The initialization is completed after a series of synchronous asynchronous programs
initOk(/ * * / data)

// Third party call
PP.init(callback)
Copy the code

Compared to using the callback function, the logic is clearer, and it is clear when the initialization is complete and the callback is triggered, so there is no need to repeat the state and callback function. It is better to output only the state and data to the third party and let the third party decide how to use it.

// Plugin code
let initOk = null
PP.init = new Promise(resolve= > initOk = resolve)
// ...
// ...
// The initialization is completed after a series of synchronous asynchronous programs
initOk(/ * * / data)

// Third party call
PP.init.then(callback).catch(console.error)
Copy the code

2.2 Chained calls

Then () necessarily returns a Promise object, which in turn has a then() method, which is why promises can be chained.

const p = new Promise(r= > r(1))
    .then(res= > {
        console.log(res) / / 1
        return Promise.resolve(2)
        .then(res= > res + 10) // === new Promise(r => r(1))
        .then(res= > res + 10) // The last then following the instance is returned each time
    })
    .then(res= > {
        console.log(res) / / 22
        return 3 // === Promise.resolve(3)
    })
    .then(res= > {
        console.log(res) / / 3
    })
    .then(res= > {
        console.log(res) // undefined
        return 'The Greatest King'
    })

p.then(console.log.bind(null.'Who made it to the end :')) // Who lived last: the greatest king
Copy the code

Since returning a Promise structure always returns the last then() of the chained call, there is no need to wrap a Promise layer around the wrapped Promise interface.

// Wrap a layer of Promise
function api () {
    return new Promise((resolve, reject) = > {
        axios.get(/ * * /).then(data= > {
            // ...
            // Has undergone a series of data processing
            resolve(data.xxx)
        })
    })
}

// Better yet: use chained calls
function api () {
    return axios.get(/ * * /).then(data= > {
        // ...
        // Has undergone a series of data processing
        return data.xxx
    })
}
Copy the code

2.3 Managing MultiplePromiseThe instance

Promise.all()/promise.race () can wrap multiple Promise instances into a single Promise instance, saving significant time when processing parallel, non-dependent requests.

function wait (ms) {
    return new Promise(resolve= > setTimeout(resolve.bind(null, ms), ms))
}

// Promise.all
Promise.all([wait(2000), wait(4000), wait(3000)])
    .then(console.log)
// After 4 seconds [2000, 4000, 3000]

// Promise.race
Promise.race([wait(2000), wait(4000), wait(3000)])
    .then(console.log)
// 2000 after 2 seconds
Copy the code

2.4 Promiseasync / await

Async/await is really just syntax sugar built on top of promises to make asynchronous code look more like synchronous code, so async/await is non-blocking in JavaScript threads, but is blocking within the scope of the current function.

let ok = null
async function foo () {
    console.log(1)
    console.log(await new Promise(resolve= > ok = resolve))
    console.log(3)
}
foo() / / 1
ok(2) / / 2, 3
Copy the code

useasync / awaitAdvantage:

  1. clean

    Write less code, and you don’t have to create an anonymous function to put in the then() method and wait for a response.

    // Promise
    function getUserInfo () {
        return getData().then(
            data= > {
                return data
            }
        )
    }
    
    // async / await
    async function getUserInfo () {
        return await getData()
    }
    Copy the code
  2. Processing conditional statements

    When an asynchronous return value is a criterion for another piece of logic, chained calls become more complex as the hierarchy adds up, making it easy to get lost in the code. Using async/await will make your code more readable.

    // Promise
    function getGameInfo () {
        getUserAbValue().then(
            abValue= > {
                if (abValue === 1) {
                    return getAInfo().then(
                        data= > {
                            // ...})}else {
                    return getBInfo().then(
                        data= > {
                            // ...})}})}// async / await
    async function getGameInfo () {
        const abValue = await getUserAbValue()
        if (abValue === 1) {
            const data = await getAInfo()
            // ...
        } else {
            // ...}}Copy the code
  3. Processing median

    Asynchronous functions often have asynchronous return values that serve only as tickets to the next logical step, which can easily become another form of “callback hell” if you go through a chain of calls.

    // Promise
    function getGameInfo () {
        getToken().then(
            token= > {
                getLevel(token).then(
                    level= > {
                        getInfo(token, level).then(
                            data= > {
                                // ...})})}// async / await
    async function getGameInfo() {
        const token = await getToken()
        const level = await getLevel(token)
        const data = await getInfo(token, level)
        // ...
    }
    Copy the code

    For multiple asynchronous return intermediate values, use promise. all to improve logic and performance.

    // async / await & Promise.all
    async function foo() {
      // ...
      const [ a, b, c ] = await Promise.all([ promiseFnA(), promiseFnB(), promiseFnC() ])
      const d = await promiseFnD()
      // ...
    }
    Copy the code
  4. Await by the spectrum

    Await ‘STR’ is equal to await promise.resolve (‘ STR ‘), and await wraps any value that is not a Promise into a Promise, which doesn’t seem to be useful, However, you can “Hold” both synchronous and asynchronous return values when dealing with third-party interfaces, otherwise using a THEN () chaining call on a non-PROMISE return value will result in an error.

useasync / awaitFaults:

Await blocks code execution in async functions and is slightly redundant in code that is not context-sensitive.

// async / await
async function initGame () {
    render(await getGame()) // Wait for the game to be executed before getting user information
    report(await getUserInfo())
}

// Promise
function initGame () {
    getGame()
        .then(render)
        .catch(console.error)
    getUserInfo() // Get user information and get the game in sync
        .then(report)
        .catch(console.error)
}
Copy the code

2.5 Error Handling

  1. Try to end chained calls with a catch to catch the error, rather than a second anonymous function. Catch (rejectionFn) is an alias for THEN (null, rejectionFn) because the specification states that if the argument in the then() method is not a function, it does nothing.

    anAsyncFn().then(
      resolveSuccess,
      rejectError
    )
    Copy the code

    In the above code, errors thrown by anAsyncFn() will rejectError normally, but errors thrown by resolveSuccess will not be caught, so it is better to always use catch.

    anAsyncFn()
      .then(resolveSuccess)
      .catch(rejectError)
    Copy the code

    If you want to catch anAsyncFn() error, you can also catch the error of the resolveSuccess.

    anAsyncFn()
      .then(
        resolveSuccess,
        rejectError
      )
      .catch(handleError)
    Copy the code
  2. Listen for unprocessed Promise errors through global properties.

    The browser environment (window) is listening for rejection status events:

    • Unhandledrejection This event is fired when a Promise is rejected and no rejection handler is provided.
    • Rejectionhandled When the Promise is rejected, if the rejection handler is called.
    // Initialize the list
    const unhandledRejections = new Map(a)// Listen for unprocessed rejects
    window.addEventListener('unhandledrejection'.e= > {
      unhandledRejections.set(e.promise, e.reason)
    })
    // Listen to the processed rejected state
    window.addEventListener('rejectionhandled'.e= > {
      unhandledRejections.delete(e.promise)
    })
    // Loop through the rejection state
    setInterval(() = > {
      unhandledRejections.forEach((reason, promise) = > {
        console.log('handle: ', reason.message)
        promise.catch(e= > {
          console.log(`I catch u! `, e.message)
        })
      })
      unhandledRejections.clear()
    }, 5000)
    Copy the code

Note: Promise.reject() and new Promise((resolve, reject) => reject()) do not directly trigger the unhandleDrejection event, Must satisfy a Promise object that has made a then() chained call.

2.6 Canceling onePromise

A Promise has no cancellation method, such as cancel(). The only way to end a Promise is to change its state using either resolve or reject. The community already has open-source Speculation that meets this need.

Or use the promise.race () mechanism to inject an asynchronous function that will time out at the same time, but the main program is still pending after promise.race () ends.

Promise.race([anAsyncFn(), timeout(5000)])
Copy the code

2.7 Application of iterators

If you want to execute a bunch of asynchronous programs in sequence, use Reduce. Each walk returns a Promise object, and the next round of await is executed in turn.

function wasteTime (ms) {
    return new Promise(resolve= > setTimeout(() = > {
        resolve(ms)
        console.log('waste', ms)
    }, ms))
}

// Waste 3, 4, 5, 3 seconds === 15 seconds
const arr = [3000.4000.5000.3000]
arr.reduce(async (last, curr) => {
    await last
    return wasteTime(curr)
}, undefined)
Copy the code

Third, summary

  1. Whenever you use asynchronous code, consider using itPromise.
  2. PromiseIs the return type of all methods inPromise.
  3. PromiseThe state change in is one-time and is recommended inreject()Pass in methodErrorObject.
  4. Make sure for allPromiseaddthen()catch()Methods.
  5. usePromise.all()Row run multiplePromise.
  6. If want tothen()catch()After all do what, can usefinally().
  7. You can combine multiplethen()Mount in the samePromiseOn.
  8. asyncThe (asynchronous) function returns onePromise, all returnPromiseCan also be considered an asynchronous function.
  9. awaitUsed to call an asynchronous function until its state changes (fulfilled or rejected).
  10. useasync / awaitContext dependencies should be considered to avoid unnecessary blocking.

The appendix

Read the original: blog.mazey.net/1642.html