I will do my best, and I promise I will give this guy a fight, and I have to win this fight.

– Manny Pacquiao

Let’s start with the following code:

function asyncFunction(){
    return new Promise((resolve, reject) = > {
        // do something
        if(alright){
            resolve()
        } else {
            reject()
        }
    })
}
Copy the code

This is a familiar way to encapsulate promises. In practice, the use of JS functions is a first-class citizen feature, and we can build on resolve to implement very flexible promise encapsulation

Suspend Resolve

Imagine a scenario in which several pieces of business logic need to wait until the user logs in successfully. This is easy, just precede the logic with an await login(), and the drawback is obvious: await login() will be called repeatedly in many places. So it’s not hard to think of a second way:

const afterLoginCallbacks = []
export async function login(){  
    await doLogin()  
    afterLoginCallBacks.forEach(callback= > callback())
    afterLoginCallBacks.length = 0
}

export function setAfterLoginCallback(fn){
    afterLoginCallbacks.push(fn)
}

// somewhere1
setAfterLoginCallback(task1)
// somewhere2
setAfterLoginCallback(task2)
Copy the code

No problem, this method implements a task queue and suspends the task to be executed together after login. We could also choose to do the same with promise:

const loginResolves = []
export async function login(){
    await doLogin()
    loginResolves.forEach(resolve= > resolve())
    loginResolves.length = []
}
export function afterLogin(fn){
    return new Promise((resolve, reject) = > {
        loginResolves.push(resolve)
    }).then(fn)
}

// somewhere1
afterLogin(task1)
// somewhere2
afterLogin(task2).then(...)
Copy the code

Thus, we create a Promise instance when we call afterLgoin methods here and there, suspend its resolve methods, and call those resolve methods when login succeeds, making all Promise instances resolve. Since afterLogin returns a Promise, you can continue with then. The purpose of this example is to show that we can save Promise resolve somewhere else and then make Promise Resolve when we need it. This gives us a more elegant solution for dealing with discrete events. Now imagine this scenario: make an instant messaging application that asks you to display a loading animation in circles next to the message bubble after sending a message, and then make the animation disappear after the message is sent successfully. Generally speaking, websocket is used in instant chat. After the sender pushes the message through the socket, the server forwards the message to the receiver, and then pushes an acknowledgement message to the sender, telling him that the message has been successfully sent. Naturally, the front-end code can be written

function sendMsg(msg, msgId, onSuccess){
    socket.push({ msg, msgId })
    const onMessageListener = ({msgId: reciptMsgId}) = > {
        if(reciptMsgId === msgId){
            onSuccess()
            socket.removeListener(onMessageListener)
        }
    }
    socket.onmessage(onMessageListener)
}

showLoading()
sendMsg('xxx'.'yyy', hideLoading)
Copy the code

Could this scene be transformed into a Promise? Unlike sending XHR requests, sockets are event-based, send events and return receipt events are discrete, we can only connect the two with an msgId, and we have to code in publish-subscribe mode, which can be uncomfortable at times, but we have promises. Using the techniques mentioned above, we can get an implementation of a Promise:

const resovleMap = {}
// Global socket event listener
socket.onmessage(function(msg){
    const targetResolve = resolveMap[msg.msgId]
    if(targetResolve){
        targetResolve(msg)
    }
})
function doSendMsg(msg, msgId){
    return new Promise((resolve, reject) = > {
        socket.push({msg, msgId})
        resolveMap[msgId] = resolve
    })
}

showLoading()
await sendMsg(msg, msgId)
hideLoading()
Copy the code

Creating a Promise instance when a push event is triggered and resolving the Promise when a return receipt event is received makes the method of sending a message wrapped as a Promise more consistent with our coding conventions

More play

Now that we can store resolve in an external variable, we can also return it as a return value, or pass it to a callback function as an argument, leading to more interesting gameplay such as asking the constructor of the callback function to decide if a Promise will resolve:

function fn(callback){
    return new Promise((resolve, reject) = > {
        callback(resolve, reject)
    }).then((arg) = > {
        // do something
    })
}

fn(function(done){
    // do something
    done(arg)
})
Copy the code

You can use this technique when doing validation of form components

Matters needing attention

As someone who has stepped on a lot of holes, here are some suggestions:

  1. Tricky tips should not be misused, there are general solutions and you may need to use them only if they make your code feel better and more readable
  2. When using Promises, make sure you have a good understanding of microtasks and make sure you can accurately predict the behavior of your program, or things could go wrong